dira ⋅ geek ⋅ girl

OmniAuth strategy for everything else

dira

OmniAuth makes it incredibly easy to authenticate users using an extensive list of external providers. But, what about using a provider that it does not support out of the box - let's say, a PHP forum?

I had to authenticate users from Pixelation into Tzigla, a Rails app that already used OmniAuth with Facebook and Twitter. So, I wrote a custom OmniAuth strategy which, in collaboration with a tiny PHP script on the Pixelation server, made this possible.

I'll first explain a bit about how OmniAuth works and then I'll show you the actual Pixelation authorization strategy.

How OmniAuth works

OmniAuth assumes that any authorization process has two phases: request and callback. Here's the request dance of an authentication process:

The request is typically a redirect to the provider's website. The provider authenticates the user and (optionally) asks permission to share information about him with the application. Then the provider redirects again to our application, sending along data about the user.

Looking more into detail, OmniAuth has two parts: the core library and strategies for using various providers. Here's how strategies fit in the grand scheme of things:

The core library intercepts all the authentication URLs, determines which is the strategy to use (based on the provider parameter in the URL), and then steps out of the way and lets the strategy decide what to do.

This means that the Facebook strategy will redirect to a Facebook URL and expect, in the callback, data about the user in the format from the Facebook specification. Of course, the Twitter strategy will be different and act according the Twitter spec, and so on.

The Pixelation authorization strategy

Since OmniAuth is doing all the dirty work, we need to implement just a few things:

  • redirect to the authentication URL on the Pixelation forum for the request phase
  • receive the user's id, username and avatar in the callback phase
  • be able to check that the callback comes from Pixelation and not from some identity thief.

First, we have to configure our provider. We need a Pixelation URL for the request phase and a shared secret known only by our app and the Pixelation forum. At the callback phase, the data about the user will be signed by Pixelation with this secret.

Like with all providers, we setup the parameters in a Rails initializer. Since we're not bundled with OmniAuth, we also need to autoload our class:

# config/initializers/omniauth.rb
module OmniAuth
  module Strategies
    # tell OmniAuth to load our strategy
    autoload :Pixelation, 'lib/pixelation_strategy'
  end
end

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, "app_name", "secret"
  provider :facebook, "app_name", "secret", :scope => ''
  # pass the 2 parameters to the constructor
  provider :pixelation, "secret", "redirect URL"
end

In the Pixelation strategy we save the received parameters…

# receive parameters from the strategy declaration and save them
def initialize(app, secret, auth_redirect, options = {})
  @secret = secret
  @auth_redirect = auth_redirect
  super(app, :pixelation, options)
end

…then use the redirect URL in the request phase…

# redirect to the Pixelation website
def request_phase
  r = Rack::Response.new
  r.redirect @auth_redirect
  r.finish
end

…and use the secret to verify the received data in the callback phase:

def callback_phase
  uid, username = request.params["uid"], request.params["username"]
  avatar, token = request.params["avatar"], request.params["token"]
  sha1 = Digest::SHA1.hexdigest("a mix of #{@secret}, #{uid}, #{username}, #{avatar}")

 # check if the request comes from Pixelation or not
  if sha1 == token
    @uid, @username, @avatar = uid, username, avatar
    super # OmniAuth takes care of the rest
  else
    fail!(:invalid_credentials) # OmniAuth takes care of the rest
  end
end

Check out the Pixelation class: http://gist.github.com/722793

Conclusion

Writing a new OmniAuth strategy was a lot easier than I expected. I hope that sharing my experience will help you better understand how OmniAuth works and how you can develop new strategies on top of it.