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.
- 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.
- 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.
- The user logs into the third party authentication app, a session cookie is set and the browser is redirected to the OmniAuth response endpoint.
- 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
Leave a Reply