OmniAuth recently reached version 1.0 and has a number of nice additions. In this episode we’ll take a look at a new strategy called OmniAuth Identity which allows users to create an account by supplying a user name and password instead of logging in through an external provider.

How Our Application Currently Works

Below is a screenshot from the application we’ll be working with. We already have OmniAuth set up with three external providers.

If we sign in through one of these providers, say Twitter, we’ll be redirected to Twitter’s website and asked if we want to authorize the application to access our Twitter account details. If we agree the application will grab our profile information from Twitter and we’ll be signed in.

Once we’ve authorized this application we won’t need to do it again and we can log in to our application through Twitter by just clicking the icon on the home page.

This is a convenient way of allowing users to sign in to but we’re excluding those who don’t want to sign in through one of these services. We should give these users the option of creating an account with a password directly in the site and this is where OmniAuth Identity comes in.

Before we do this we’ll walk through some of the application’s code to give you an idea of how it works. The source code is based on the application from episode 241 and if you’re unfamiliar with OmniAuth it’s worth taking a look at that episode first.

Since episode 241 was written there have been some changes to OmniAuth and one of the most significant is in the gemfile. Each provider now has a separate gem for OmniAuth so its necessary to include the right gem for each provider we want to support.

The application has a SessionsController whose create action is triggered as the OmniAuth callback. In it we create a user based on the OmniAuth hash and store that new user’s id in a session variable.

This method first checks to see if a user with the selected provider and user id exists. If so that user is returned, if not a create_with_omniauth method is called which creates that new user based on information from the OmniAuth hash that’s passed in. There’s one change to note in the hash. The info parameter was called user_info in earlier versions so if you’re upgrading an application to use OmniAuth 1.0 your code may break.

Adding OmniAuth Identity to an Application

That’s all the code that’s necessary to handle authentication through OmniAuth. Next we’ll add OmniAuth Identity to so that users can create an account without having to use an external authentication service. As we mentioned earlier each OmniAuth provider requires a separate gem so the first thing we’ll need to do is add the omniauth-identity gem to the gemfile.

This gem relies on bcrypt-ruby for password hashing, but it’s not a dependency so it’s necessary to add bcrypt-ruby to the gemfile as well. There should be a comment in the gemfile that we can uncomment to add this gem. As ever we’ll need to run bundle to make sure all the gems are installed.

Next we’ll need to go to OmniAuth’s initializer and add the identity provider. We don’t need to add any parameters to this provider for now.

OmniAuth Identity’s README shows us what we need to do to get the Identity model to work with various ORMs, including ActiveRecord. We’ll need to change the model so that it inherits from OmniAuth::Identity::Models::ActiveRecord instead of ActiveRecord::Base.

/app/models/identity.rb

classIdentity < OmniAuth::Identity::Models::ActiveRecordend

We’re almost done. All we need to do now is make a link to the new provider from the sign in page under the links for logging in through an external provider.

/app/views/sessions/new.html.erb

<p><strong>Don&rsquo;t use these services?</strong><%= link_to "Create an account", "/auth/identity/register"%> or
<%= link_to "login", "/auth/identity"%> with a password.
</p>

We need to restart the server for the changes to OmniAuth to be picked up. Once we have when we visit the sign-in page we’ll see new links for registering or logging in. First we’ll register a new account.

OmniAuth Identity provides a simple registration form with a clean interface and once we’ve filled it in correctly we’ll be signed in and taken to our profile page.

The login page looks similar, but has just two fields. The login field expects the email address we entered when we registered, though this isn’t obvious.

Creating Custom Registration and Login Forms

Registration and logging-in pretty much just work and because Identity uses the same OmniAuth interface as the code for the other providers we don’t have to change much of our core application to add this functionality. That said, there are a few pitfalls we should be aware of. If we have validations on our Identity model the user won’t see any errors if they fill in the form incorrectly. To demonstrate this we’ll add some validations to the Identity model now.

If we enter an invalid email address on the registration form now and try to register we’ll be taken back to the form with no indication of what we did wrong.

Similarly, the login form doesn’t make it clear that the “Login” field expects the user’s email address. There are options to customize the way this form looks but nothing particularly extensive. A better solution is to create our own custom forms in to the app itself to give us complete control over how they’re rendered and how they behave.

We’ll move the login form first. Instead of displaying a link to OmniAuth’s login form on the sign-in page we’ll show our custom login form.

This form POSTs to /auth/identity/callback which is where OmniAuth’s own form submits its data. We also give the form fields the same names they have on OmniAuth’s form. When we reload the sign in page now we have a login form and we can use it to enter our details and log in to the site just as we did with the other form.

We can take a similar approach to replacing the registration form, although we’ll need to create a new controller to do this.

termnial

$ rails g controller identities

We’ll treat this controller as a resource so we’ll modify the routes file to add it as such there.

We’ll give the controller a new action. There’s a chance that if the validation for the registration form fails the identity object will be stored in a Rack environment variable so we’ll fetch it from there so that we can show any error messages on it.

The form in this template POSTs to /auth/identity/register. At the top of the form we display any errors; below it are the same fields that OmniAuth’s registration form has. We populate the name and email fields if they exist in the current identity object.

We’ll need to alter the “Create an account” link on the sign in page so that it points to our new form instead of OmniAuth’s default.

When we click this link now we’ll be taken to our new form and we can use it to register a new account.

Our new form works well for creating new valid accounts but if we enter some invalid information then submit the form we’ll be redirected back to OmniAuth’s registration form. To fix this we’ll need to go to OmniAuth’s initializer file and modify the identity provider we added earlier.

The option we’ve added, on_failed_registration, takes a Rack application as an argument. We’ve set it to point to the new action in the IdentitiesController so that our new form is shown again. There’s an issue with calling this action directly: in development mode the action won’t always be reloaded so we’ve wrapped the code in a lambda to stop the action being cached.

We’ll need to start the Rails server for these changes to be picked up, but once we have the form will behave as we expect it to when we enter invalid information.

We now have OmniAuth identity working smoothly. Creating the custom forms took quite a bit of work but this is a still a nice solution if you’re already using OmniAuth and just need to add account registration.