Using OmniAuth to Authenticate for Doorkeeper

In Ruby, if you want to set up a Oauth provider the Doorkeeper Gem is a popular solution. We had a application where we wanted to provide our end users with a oauth interface for our main authentication/authorization, but delegate the process of logging in to a few third party systems. By doing this, all of our client code could use Oauth to authenticate and authorize against our endpoints and would only need to have limited knowledge of the third party login options.

In our case the third party systems were using cookie based session credentials and redirect back to a specified url in the request. We eventually came up with a login workflow that looked like this.
oauth seq - Page 1

  1. The user starts out on a  page with a link to login to each provider.  The link contains a parameter called redir telling it where to go to after the user has successfully logged in.
  2. When the user clicks the link they are directed to the OmniAuth provider specified in the link.  The provider takes the redir url and puts it in a cookie and then redirects to the provider with a redir= option that sends it back to the OmniAuth response endpoint associated with it.
  3. The user logs into the third party authentication app,  a session cookie is set and the browser is redirected to the OmniAuth response endpoint.
  4. The response code checks the cookie,  creates a Doorkeeper access grant,  stores the provider cookie in the provider table,  reads and deletes the cookie that had the initial redir url and finally redirects to that URL,  setting the access grant in a cookie that can be used in a web service call to against the Doorkeeper endpoints following the standard Oauth workflow using a Authorization Code Access Grant.

Show Me The Code

These examples assume that you have installed OmniAuth and Doorkeeper including the Doorkeeper migrations.

First you will need to create a OmniAuth strategy.  See this post for how to do with without having to create a OmniAuth strategy gem.

module OmniAuth
  module Strategies
    class Application1

      include OmniAuth::Strategy

      args [:authentication_url]

      def request_phase
        request = Rack::Request.new env
        response = Rack::Response.new
        response.set_cookie 'myapp_redirect_to_when_done', { value: request.params['redir'], expires: Time.now + 10.minutes}
        response.redirect "#{options.authentication_url}?redir=#{full_host + script_name + callback_path}"
        response.finish        
      end

      def callback_phase
       ....
      end

      private

      def strategy_id
        'application_1'
      end
    end
  end
end

The request_phase method is called when you go to the /auth/application1 endpoint. This code stores the original redir url in a cookie, redirects to the application1 to authenticate and adds a redir= to the url so that the application can redirect back to this strategy when the user is done logging in.
 
After the user has logged in to the external application and is returned back we handle the callback_phase with the following code

      def callback_phase
        request = Rack::Request.new env
        cookies = request.cookies
        response = Rack::Response.new

        provider = Provider.create({provider_id: strategy_id,
                                    metadata: cookies['external_application_cookie']
        })

        doorkeeper_configuration = Doorkeeper.configuration

        doorkeeper_app = Doorkeeper::Application.where(name: APP_CONFIG["#{strategy_id}_doorkeeper_app"]).last

        doorkeeper_auth_code = Doorkeeper::AccessGrant.create!({
            application_id: doorkeeper_app.id,
            resource_owner_id: provider.id,
            scopes: 'public',
            expires_in: doorkeeper_configuration.access_token_expires_in,
            redirect_uri: doorkeeper_app.redirect_uri
          })

        response, doorkeeper_auth_code, doorkeeper_app = params[:response], params[:doorkeeper_auth_code], params[:doorkeeper_app]
        response.delete_cookie 'myapp_redirect_to_when_done', path: '/auth'
        response.delete_cookie 'application1_token'
        response.delete_cookie 'application1_uid'
        response.delete_cookie 'application1_secret'
        response.set_cookie 'application1_token', { domain: APP_CONFIG['auth_cookie_domain'], value: doorkeeper_auth_code.token, expires: Time.now + 1.year, path: '/'}  
        response.set_cookie 'application1_uid', { domain: APP_CONFIG['auth_cookie_domain'], value: doorkeeper_app.uid, expires: Time.now + 1.year, path: '/'}  
        response.set_cookie 'application1_secret', { domain: APP_CONFIG['auth_cookie_domain'], value: doorkeeper_app.secret, expires: Time.now + 1.year, path: '/'}  
         
        response.redirect cookies['application1_redirect_to_when_done']
      end

The big thing to note here is that we created a provider table to store the cookie metadata from the other application. We then associate it to the Doorkeeper access grant via the resource_owner_id field in the AccessGrant. The access grant is stored in a cookie and the user is redirected back to their application ready to hit the Doorkeeper access points.
 
Our final class looks like this

module OmniAuth
  module Strategies
    class Application1

      include OmniAuth::Strategy

      args [:authentication_url]

      def request_phase
        request = Rack::Request.new env
        response = Rack::Response.new
        response.set_cookie 'myapp_redirect_to_when_done', { value: request.params['redir'], expires: Time.now + 10.minutes}
        response.redirect "#{options.authentication_url}?redir=#{full_host + script_name + callback_path}"
        response.finish        
      end
     
      def callback_phase
        request = Rack::Request.new env
        cookies = request.cookies
        response = Rack::Response.new

        provider = Provider.create({provider_id: strategy_id,
                                    metadata: cookies['external_application_cookie']
        })

        doorkeeper_configuration = Doorkeeper.configuration

        doorkeeper_app = Doorkeeper::Application.where(name: APP_CONFIG["#{strategy_id}_doorkeeper_app"]).last

        doorkeeper_auth_code = Doorkeeper::AccessGrant.create!({
            application_id: doorkeeper_app.id,
            resource_owner_id: provider.id,
            scopes: 'public',
            expires_in: doorkeeper_configuration.access_token_expires_in,
            redirect_uri: doorkeeper_app.redirect_uri
          })

        response, doorkeeper_auth_code, doorkeeper_app = params[:response], params[:doorkeeper_auth_code], params[:doorkeeper_app]
        response.delete_cookie 'myapp_redirect_to_when_done', path: '/auth'
        response.delete_cookie 'application1_token'
        response.delete_cookie 'application1_uid'
        response.delete_cookie 'application1_secret'
        response.set_cookie 'application1_token', { domain: APP_CONFIG['auth_cookie_domain'], value: doorkeeper_auth_code.token, expires: Time.now + 1.year, path: '/'}  
        response.set_cookie 'application1_uid', { domain: APP_CONFIG['auth_cookie_domain'], value: doorkeeper_app.uid, expires: Time.now + 1.year, path: '/'}  
        response.set_cookie 'application1_secret', { domain: APP_CONFIG['auth_cookie_domain'], value: doorkeeper_app.secret, expires: Time.now + 1.year, path: '/'}  
         
        response.redirect cookies['application1_redirect_to_when_done']      
      end

      private

      def strategy_id
        'application_1'
      end
    end
  end
end

About Me: I am a Atlanta based, mobile/Android/IOS/AngularJS/Ruby developer, polyglot programmer, founder of Polyglot Programming Inc., wearable technology enthusiast and am interested in the internet of things. You will often find me purr programming and I regularly speak at conferences around the world. I am available for hire! More Posts

Follow Me:
TwitterLinkedInGoogle Plus

I am a Atlanta based, mobile/Android/IOS/AngularJS/Ruby developer, polyglot programmer, founder of Polyglot Programming Inc., wearable technology enthusiast and am interested in the internet of things. You will often find me purr programming and I regularly speak at conferences around the world. I am available for hire!

Posted in Architecture, Development, rails, ruby, scaling Tagged with: , , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

*