Social Login with OmniAuth and Rails 5.0

Ajay Ramesh
8 min readDec 30, 2016

--

Social login is the concept of using a third-party like Google, Facebook, or Twitter to authenticate and store users in your web application. In other words, users who have accounts on the aforementioned sites, and more, can access your application without having to create a separate account. Existing guides either used older versions of Ruby, had missing features such as authenticated routes, or had lots of unnecessary code. I’ve gathered what I have learned from other tutorials and put them together here.

If you are deciding whether or not to spend the next few minutes working through this tutorial, I will try to help make that decision for you. By the end of this tutorial, you will have implemented a single provider (Google) social login with the ability to authenticate specific routes in your Rails app. If that sounds enticing to you, please keep reading.

What’s Rack middleware?

You may have seen this term when doing your own research about authentication in Rails. Rack is a specification for interfacing the HTTP protocol with “Rack apps” like Rails and Sinatra. Rack apps run on Rack servers like WEBrick and Puma. Rack middleware is code that runs “in between” the the raw HTTP request sent to the server, and the nicely formatted request Rack generates for Rack apps. Rack middleware such as Devise and OmniAuth employ other Rack middleware to embed information like the auth hash. I believe this is sufficient knowledge to use Rack middleware so I won’t delve into it much deeper. Please check out this wonderful article by Gaurav Chande if you want to learn more about Rack.

Devise or OmniAuth or both?

If you came here by searching “Rails authentication” or the like, you have probably run into Devise, a popular authentication solution for Rails. I had originally thought that OmniAuth and Devise were competing solutions but they are actually quite different. OmniAuth describes itself as “a black box that you can send your application’s users into when you need authentication and then get information back”. It features an extensive collection of strategies which makes it easy to integrate with other services. Think, Passport.js but for Rails. On the other hand, Devise is a fully fledged MVC framework that goes as far as to generate form views and user models. It even has a rich set of helper functions like current_user and user_signed_in? which are not included in OmniAuth. (This guide will implement both these helpers manually). Devise is great if you want to build a custom account registration system, and have OmniAuth as an additional option. OmniAuth is great if you want to plug and play authentication into an existing application or if you want to use your own MVC components.

1. Register your app with Google

In order to access Google APIs, you must register your application. Navigate to console.cloud.google.com and create a project. It may take up to a minute for it to get created. Make sure your new project is selected in the upper left corner. Search for the Google+ API and enable it. Do the same for the Contacts API. The sidebar on the left should have a “Credentials” button, click it, and hit “Create credentials” > “OAuth client ID”. Before you create the credentials, you will be prompted to create the consent screen. The only mandatory field is the app name, so go ahead and fill that in. Now, you will be able to create your credentials. The application type is “Web application”. You can give it a name of “Rails Server” or something descriptive. The “Authorized redirect URIs” should have one URL of the form:

http://localhost:3000/auth/google_oauth2/callback

The OAuth2.0 protocol which OmniAuth abstracts, relies on a callback URL to pass the authenticated user object into your application. OmniAuth handles the necessary juggling to get that user object in the first place. The developer should only be responsible for handling that user object, which we will do in a bit.

After filling out this form, you will be presented with your Client ID and Client Secret, both of which are used for authorizing your application to use Google’s APIs.

2. Create the Rails app

Creating this demo app is no different than creating any other Rails app. Go ahead and run

rails new google-omniauth-tutorial

… but try to pick a more elegant name.

The only gem we’ll need for this application is omniauth-google-oauth2, which is the Google strategy for OmniAuth. Add the following to your Gemfile .

gem ‘omniauth-google-oauth2’

and then run bundle install .

3. Configure OmniAuth

Create config/initializers/omniauth.rb . As the name suggests, all code inside of initializers is run when the application starts up. This code adds OmniAuth to the Rack middleware. The provider method also accepts an options hash described here.

Replace GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET with the values generated in the Cloud Console. Believe it or not, OmniAuth itself is now configured! The bulk of our work will now go into handling the authentication request from Google.

4. Create the User

Unlike Devise, OmniAuth does not assume our User model, so we still need to create that. The auth hash returned contains a ton of information about the User. I will choose to store the provider, uid, email, first name, last name, and the picture URL. Let’s create our model and run the migration. Note that omitting the field type implies a string value.

rails g model User provider uid email first_name last_name picture

rake db:migrate

We can use the uid returned by the auth hash to identify our user in the database. Let’s write a helper function in models/user.rb to do just that.

The class method find_or_create_from_auth_hash will use the hash sent by Google to look up our user model in the database. The function first_or_initialize.tap returns the User object if it finds one, updates the user if any of the information has changed, or creates and saves a new one if the user didn’t exist at all. We can identify a user by the provider and the uid . The uid really should be enough to identify the user, but we can avoid the rare collision of more than one provider having the same uid.

Now we are done creating the model, so let’s configure the routes.

5. Configure the Routes

The routes provide a nice summary of how everything works together. Add the following code to your routes.rb.

Let’s talk about each route so that we can be extra clear. Feel free to skip ahead if you can figure out what we’re doing.

login is a convenience route to go directly to /auth/google_oauth2 which is the mandatory login route for OmniAuth. /auth/google_oauth2 will display Google’s account selection or login menu.

logout has the action session#destroy which, as the name implies, destroys the current session, effectively logging out.

auth/:provider/callback creates a new session with the User object (turned auth hash) returned by Google. It accepts a parameter :provider in case you had multiple providers. If you decide to include multiple providers, then login must redirect to a generic provider selection menu, rather than straight to Google. In our case, auth/google_oauth2/callback would have also worked fine.

auth/failure is requested by the provider if the user fails to accept the requested permissions. In our case, we redirect to root if this occurs.

home a publicly available page that contains the Login button.

me an authenticated route that serves information specific to the current user.

6. Create the controllers

We will be creating three controllers, Session, Home, and Me . Let’s create Home and Me first because they don’t require much explanation.

rails g controller Home

rails g controller Me

According to routes.rb, both Home and Me have only one action, show.

The line before_action tells Rails to run the :authenticate method before any action is called. We will define :authenticate soon.

The rails controller generator creates views which we do not need for the Session, so go ahead and create controllers/sessions_controller.rb and add the following code.

There are two actions, create and destroy. According to routes.rb, the create action is called upon the request auth/:provider/callback. That means that create receives the auth hash from the provider, or Google in our case. As you can see, we are calling the find_or_create_from_auth_hash class method we wrote earlier. The hash itself is stored in env["omniauth.auth"]. We also put the User ID into the globally accessible session hash. This allows a user to make multiple requests to the app without having to log in. Finally, after the user is logged in, we redirect them to the :me route to view their account specific information.

destroy removes the :user_id from the session and redirects the user to root. The next authenticated request the user makes will require a login.

We also need to create the authenticate function in controllers/application_controller.rb . Since all other controllers are subclasses of ApplicationController, public methods and variables you define here are inherited by them.

As you can see, the authenticate method which is the before_action of Me, will redirect the user to /auth/google_oauth2 if they are not already signed in. The helper function current_user checks the session hash for the :user_id we inserted in sessions#create and pulls up matching User. user_signed_in? is another convenience method you can use in your controllers. We set current_user as a helper_method so that it’s usable in .erb views. We’re finally done with the controllers!

7. Create the Views

One of my pet peeves is seeing too much .erb and .scss code on tutorials like this so I’ll keep mine short!

Since / or /home is accessible by anyone, we need a way for people to login. Just add the following to views/home/show.html.erb since show is the only action we defined.

Now, /me is an authenticated route that has access to the currently logged in user, so we can display some of their data. Add this code to views/me/show.html.erb.

This code simply iterates through the attributes hash and displays the key and values. You can get fancy by actually rendering the picture in an <img> tag and adding some styling.

localhost:3000/me, The fruits of your labor

There you have it! The Rails application you just built has social login with Google, as well as an authenticated route. If you have any questions or run into technical issues, please leave a comment and I will try my best to resolve them.

Ajay Ramesh

Credits

Thanks to these sources for teaching me a new technology that I’ve come to love! Please check them out to expand your knowledge further than what my article provides.

Further Reading

--

--