Writing a Non-Gemified Strategy for OmniAuth

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.

About Me: I am a Atlanta based, native Android/IOS developer with AngularJS/Ruby experience and am founder of Polyglot Programming Inc.. Many of my projects focus on IOT and Wearables. 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, native Android/IOS developer with AngularJS/Ruby experience and am founder of Polyglot Programming Inc.. Many of my projects focus on IOT and Wearables. You will often find me purr programming and I regularly speak at conferences around the world. I am available for hire!

Posted in Development, gems, rails, rails, ruby, scaling Tagged with: , , , , , , , ,
2 comments on “Writing a Non-Gemified Strategy for OmniAuth
  1. Thiago Rodrigues says:

    Thank you, this article helps me a lot.

  2. Very nice. Thank you!!

    Martin

Leave a Reply

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

*