A couple of weeks ago episode 233 [watch, read] covered a service called Janrain Engage, a central location for managing authentication services such as Twitter, OpenID and Facebook. It is a great service but one of its downsides is that it acts as a middle man between your application and the authentication providers. A better solution would be a gem or plugin that would allow us to use third-party authentication in our app without requiring an external service.

Such a gem has recently appeared and it’s called OmniAuth3. OmniAuth provides a single way to authenticate against a number of different services. If the service you want to authenticate against isn’t supported then OmniAuth makes it easy to add your own provider. OmniAuth is a collection of Rack middleware which gives us a lot of flexibility in how we can use it.

There’s a great article on the Rails Rumble blog that explains in detail how to add OmniAuth to a Rails application. The article shows you how to create authentication from scratch, but here we’ll show you how to integrate OmniAuth into an application with an existing authentication solution. Here we’ll use Devise, but you could just as easily integrate with Authlogic or some other custom authentication.

Adding OmniAuth to Our Application

We’ll be working with the episode we wrote in episode 209 which is a simple to-do list application that uses Devise to handle user authentication. The “Sign up” and “Sign in” links take the user to a page where they can log in or sign up by supplying a username and password. We’ll start off by signing in to an existing account as this will make it simple as we develop other types of authentication such as Twitter.

OmniAuth comes as a gem and we’ll can include it in our application in the usual way by modifying the Gemfile.

We can then run bundle install (or just bundle) to install the gem and its dependencies. OmniAuth has a number of dependencies but bundler will take care of installing them all.

The next step is to go into the application’s /config/initializers directory and create a new file. We’ll call it omniauth.rb but the name isn’t really important. In this file we’ll add OmniAuth::Builder to our application’s middleware and define the providers that our application will use to authenticate against.

In this application we’re only going to use Twitter, even though there are many providers we could choose from, so we can remove the other two lines. In order to support authentication through Twitter we’ll need to set up our application with Twitter by visiting their developer site and registering it. The registration form is easy to fill in and once we’ve registered our app we’ll be given the key and secret that we’ll need to paste to the initializer file.

If we start up our application’s server we’ll already be able see OmniAuth in action. If we visit the URL /auth/twitter we’ll be redirected to Twitter and asked if we want to grant access to this application.

If we click “Allow” we’ll be redirected back to our application with the URL /auth/twitter/callback. Our application needs to handle this URL to deal with what happens after a user has logged in. How we handle this response is entirely up to us and this is what makes OmniAuth so flexible. In this case we’ll create a separate resource to handle the response that we’ll call Authentication.

We could create the model and controller separately but to make things easier we’ll use Ryan Bates’ Nifty Scaffold generator to create the model, view and controller in one go. The Authentication resource will have a user_id column, a provider column that will store the name of the provider, e .g. “Twitter” or “Facebook” and a uid column which will store the provider’s user identifier. For the controller we’ll want index, create and destroy actions.

The scaffold generator also created an AuthenticationsController which we can use to handle the OmniAuth response. We’ll map the response’s URL to the controller’s create action by modifying the routes file.

If we visit /auth/twitter now and authenticate on the Twitter site we’ll see that we get a large amount of information back.

The information returned is a hash of nested hashes. At the top of the list are the provider and uid fields that we’re interested in. We’ll store both the provider name and the uid in our Authentication model. Down at the bottom of the file is some user information that could be useful to store in the User model.

terminal

user_info:
nickname: eifion
name: Eifion
location: North Wales
image: http://a1.twimg.com/profile_images/434158309/Adium_Icon_normal.png
description: Web developer using .Net and Windows by day and Ruby and Rails on OS X the rest of the time. I run http://asciicasts.com
urls:
Website: http://asciicasts.com

Storing The Authentication Information

We’ll need to modify the create action and change its behaviour depending on the user’s current status, but we’ll look at the simplest case first: when a user is currently logged in we just want to add this new authentication to their user account.

In the create action we get the authentication information into a hash then create a new Authentication for the user based on two of the parameters from the information returned by the authentication provider. We’ll then create a flash message and redirect back to the index action.

If we visit /auth/twitter and authenticate now we’ll be redirected to the index page where we’ll see the details of the new authentication we just added with the correct provider and uid.

Improving The Look of The Index Page

There’s a really useful GitHub account call Authbuttons that has icons for many different authentication services. We can use these to improve the look of the page where the user chooses the authentication method they use to log in. For convenience we’re going to handle all of this inside the AuthenticationsController’s index action but in a production application you might want do put this in a separate page.

Before we do that, however, we’ll need to make a change to the code in the index action. The code that the scaffolding generated will fetch all of the authentications. We’ll change it so that it only fetches those for the current user.

/app/controllers/authentications_controller.rb

defindex@authentications = current_user.authentications if current_user
end

Now is also a good time to make a similar change to the destroy action so that it can’t destroy authentications that don’t belong to the current user.

If we reload the authentications page now it will look quite a lot better.

Our application doesn’t currently support all of the services shown above but the screenshot above shows us what it would be like if it did. While this page now looks a lot better it does have a bug in it. If, when we’re logged in and authenticated via Twitter, we try authenticating again a second authentication record is created when it should use the existing one.

This is a fairly easy problem to fix. All we need to do is modify the AuthenticationController’s create action so that it uses find_or_create_by_ to see if there is an existing authentication before creating a new one.

Now if we remove the second authentication and then authenticate via Twitter again we’ll still only have one authentication against the user.

One major requirement that we haven’t yet tackled is authenticating when the user is not currently signed in. If we look in the create action above we’ll see that the code assumes that we have a current user. So what happens when someone comes to the site for the first time and tries to sign in via Twitter? We’ll cover this problem in the next episode.