Social Login with OmniAuth and Rails 5.0
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.
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.
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.
- Rich on Rails: https://richonrails.com/articles/google-authentication-in-ruby-on-rails This provided me a great starting point to implement OAuth2.0 in my Rails app, and I’ve used some of their code in this tutorial.
- SO User JagdeepSingh: http://stackoverflow.com/a/41373908/896112 Helped me with some issues regarding authenticated routes.
- omniauth Wiki: https://github.com/omniauth/omniauth/wiki/Upgrading-to-1.0
Further Reading
- Twilio Blog: https://www.twilio.com/blog/2014/09/gmail-api-oauth-rails.html If you want to use Google APIs for more than just login, you need to handle token refresh
- omniauth Wiki: https://github.com/omniauth/omniauth/wiki/managing-multiple-providers If you want to use multiple providers, ie: login with Facebook, Twitter, and Google