The OmniAuth gem tends to be one of the go-to tools in a Ruby developer’s toolbox when you want to authenticate against multiple systems. It has been around for a long time and is very well-documented. When you want to integrate it with another provider, you simply add in a strategy, configure it, and things work! On the main OmniAuth site you can even find a number of pre-defined strategies. That being said, there are instances when you might need to write your own strategy.
The documentation contains a lot of great information about writing a strategy as a gem. While that can be very useful, you may run into a situation where you want to develop a strategy as part of the application, and not simply as a gem. Unfortunately, that is where the current documentation falls short, so we’ve created a simple tutorial to demonstrate how to go about writing a non-gemified strategy.
For this example, let’s assume that we have a custom third-party login system that sets a cookie to let us know when a user is logged in. We would like to use this system as a single sign-on solution for our Rails application.
First, we include the OmniAuth gem in our project’s Gemfile.
gem 'omniauth'
Next, we create a strategy class called HoneyBadger. An OmniAuth strategy must be placed in a module named OmniAuth::Strategies. For our project, we put this in lib/strategies/honey_badger.rb.
require 'omniauth'
module OmniAuth
module Strategies
class HoneyBadger
include OmniAuth::Strategy
def request_phase
...
end
def callback_phase
...
end
end
end
end
Notice that we add two methods: a request_phase and callback_phase. These will be used when we call in to our endpoint. We can now set up our config/initializers/omniauth.rb file, which will be used to configure our OmniAuth endpoints.
module OmniAuth
module Strategies
autoload :HoneyBadger, Rails.root.join('lib', 'strategies', 'honey_badger')
end
end
Rails.application.config.middleware.use OmniAuth::Builder do
provider :HoneyBadger, "https://www.beelarvae.com/login"
provider :developer
end
This loads up the strategy when the application loads and configures it as one of the supported OmniAuth strategies. OmniAuth uses Rack middleware to intercept all calls to /auth/:id. The :id is matched to the provider name that is configured in provider and autoload in the omniauth.rb file. When we go to /auth/Honeybadger, OmniAuth calls into the HoneyBadger strategy request_phase method.
Now, let’s assume that our HoneyBadger third-party login solution takes a parameter called redir that tells it where to redirect to once the user has logged in. Our request_phase method will look like this:
args [:authentication_url]
def request_phase
response = Rack::Response.new
response.redirect "#{options.authentication_url}?redir=#{full_host + script_name + callback_path}"
response.finish
end
You will notice that here we add args [:authentication_url] to the code. This is used to allow us to pass configuration parameters into our strategy via the provider directive in the omniauth.rb file. In this case, we are getting our authentication URL that we passed in via our provider directive. The method redirects the user to the HoneyBadger provider to login with a redirect URL that will send them back to us.
Next, in our routes, we add a callback path.
get "/auth/:provider/callback", to: "sessions#create"
This automatically creates the named route called callback_path that we reference in the request_phase method of our strategy. Upon a successful login the user will be redirected to this path that corresponds to a create method on a sessions controller. The redirect URL in the request_phase will be http://myhost/auth/HoneyBadger/callback. However, before that’s called, the strategy’s response_phase method will first be called.
def callback_phase
request = Rack::Request.new env
cookies = request.cookies
response = Rack::Response.new
if cookies['honey_badger'] != nil
# code to set a devise/warden or some other local login session
response.redirect some_application_url
response.finish
else
response.status = 401
response.finish
end
end
In this case, we don’t need to have a sessions_controller because we never let the callback_phase code fall through to it. If we hadn’t added the response.finish line to the end of our callback_phase, it would fall through to the controller we named in our routes. Our method checks for the cookie, and if it has been set, sets a Devise session and redirects to another URL in the application. If not set, the user gets back to our URL without a cookie and we return HTTP Error 401 Unauthorized.
The final strategy file will look something like this:
require 'omniauth'
module OmniAuth
module Strategies
class HoneyBadger
include OmniAuth::Strategy
args [:authentication_url]
def request_phase
response = Rack::Response.new
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
if cookies['honey_badger'] != nil
# code to set a devise/warden or some other local login session
response.redirect some_application_url
response.finish
else
response.status = 401
response.finish
end
end
end
end
end
In conclusion: It took a little bit of digging to distill the strategies down to these core pieces. Hopefully this will make it easier for you to add your own custom strategies that may not be appropriate for full gemification.
Thank you, this article helps me a lot.
Very nice. Thank you!!
Martin