Authenticating with Google
I have a brilliant idea. I am going to build a site that makes *insert verb*ing
easier for *insert noun*
. There’s a problem though: I need people to be able to login, but I don’t want to deal with registering users, resetting their passwords, etc.. Therefore, I have decided that using Google login is the best option. How can I implement this?
With a combination of Devise and Omniauth, the process is not as painful as it sounds. Essentially, I am going to use the process laid out bu the OAuth 2.0 specification to implement authentication for my NounVerber.
Pull Up Your Suspenders
rails new
is nice, but I like a little more functionality out of the box, so I am going to use thoughtbot’s Suspenders gem. Let’s create our app:
$ gem install suspenders
$ suspenders login_with_google
# lots and lots of rails app creation output
$ cd login_with_google
First, we need to create some pages to visit:
home
– root page, which is not secured.secure
– only viewed with login.
We’ll create a pages
controller that has these methods. Obviously, this is just a placeholder, as a Google login for static content would be frivolous:
$ rails g controller pages home secure
Set the root page to pages#home
:
# config/routes.rb
Rails.application.routes.draw do
root to: 'pages#home'
get 'pages/secure'
end
Awesome. But before we can get into actually creating the authentication, we need to create a User model. We are only using Google’s API for authentication purposes, so we’ll only need the following attributes:
uid
– unique provider-given (Google) id for our userprovider
– the authentication providername
– name saved from their Google profileemail
– email saved from their Google profile
Let’s generate that model:
$ rails g model user uid provider name email
This should create our User model. After checking the migration, we can rake db:migrate
without worry.
Note: As Suspenders tries to be Heroku-ready, you must have PostgreSQL installed on your computer in order for your migrations to work. If you just want to user SQLite3, then swap the gems and database configurations accordingly.
Adding Devise and Omniauth
Now that we have pages to display and users correctly modeled in the database, it’s time for the fun part: adding Devise and Omniauth.
First, add the gems:
# Gemfile
# ...
gem 'devise'
gem 'omniauth-google-oauth2'
# ...
The gem omniauth-google-oauth2 is what does the magic. Now bundle install
and we’re good to go!
In order for this to work properly, setup a few things:
- Install Devise
- Setup the Rack Middleware
- Add Devise routes
- Add Devise to
User
model - Create callbacks controller
- Add authentication to secure pages
Installing Devise
Devise provides an installation command for easy setup:
$ rails g devise:install
Setting Up Omniauth Rack Middleware
We need to create an omniauth middleware initializer to authenticate our client application to Google. This is done with a client ID and secret:
# 'config/initializers/omniauth.rb'
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"]
end
Whoa there, wait a minute! Did we just ask for credentials that don’t exist yet? Yes, we did. We need to have Google generate them for us.
First, go to https://console.developers.google.com/ and create a project. Then, underneath the sidebar’s “API’s & auth”, click “Credentials” and then click “Create new Client ID”. Now copy and paste the credentials you get into your .env file as GOOGLE_CLIENT_ID
and GOOGLE_CLIENT_SECRET
. Suspenders supplies the dotenv gem that allows us to put our environment variables into a .env file that is executed by Rails on startup. It’s a good practice that ensures we don’t put secrets into source control.
Another thing that may trip you up later is enabling APIs. For our purpose, we just need the Google+ API. So go to your project page on Google, click “APIs”, then enable Google+.
Adding Devise Routes
Devise creates the devise_for
method to make custom routes for authentication, and we need to add routes to handle the OAuth flow:
# 'config/routes.rb'
devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
Notice that we defined a controller that doesn’t exist yet. We’ll get to that later.
Adding Devise to User Model
Devise has another helper method, devise
, that configures our model to use Devise features. Add the following to the User
model:
# 'app/models/user.rb'
devise :omniauthable, omniauth_providers: [:google_oauth2]
Creating the Callbacks Controller
When a user logs in, they’re sent to the provider (Google in this case) and then back to the original site at a callback URL. This callback does authentication for the user based on data from the provider. Let’s create this callback:
# 'app/controllers/users/omniauth_callbacks_controller.rb'
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
@user = User.from_omniauth(request.env['omniauth.auth'])
if @user.persisted?
sign_in_and_redirect root_path, event: :authentication
else
redirect_to root_path, flash: { error: 'Authentication failed!' }
end
end
end
The method google_oauth2
in the callback controller will apply only to the route: /users/auth/google_oauth2/callback
. When this method is called, it finds an existing user or creates a new one. If the user exists or was just created, the user is logged in and redirected to the root path. Otherwise, the user is redirected back to the root and an error is flashed.
In a more complex application, the redirect paths would most likely be different, but we’ll use the root page for our purposes.
Wait a minute! We called a method named User.from_omniauth
that doesn’t exist! Let’s go into the User model and create it. This method will take a hash of user arguments and find or create a new user:
# 'app/models/user.rb'
...
def self.from_omniauth(auth)
where(provider: auth[:provider], uid: auth[:uid]).first_or_create do |user|
user.name = auth[:info][:name]
user.email = auth[:info][:email]
end
end
...
Adding Authentication to Secure Pages
We’ve done a lot so far, so let’s rails s
and test out the authentication. Open up a browser and go to http://localhost:3000/users/auth/google_oauth2
and go through the steps (you know the drill). If it works, you’re redirected to a blank page! How exciting! Almost revolutionary! But seriously though, it looks to the average user as if they did absolutely nothing.
We need to add a little login info section to our home page. Open up app/views/home:
Boring.
# 'app/views/pages/home.html.erb'
<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>
Exciting!
# 'app/views/pages/home.html.erb'
<div class="container">
<% if user_signed_in? %>
<p>Welcome back, <a href="mailto:<%= current_user.email %>"><%= current_user.name %></a>!</p>
<% else %>
<p><%= link_to 'Sign in.', user_omniauth_authorize_path('google_oauth2') %></p>
<% end %>
</div>
<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>
Now, if we visit the home page here’s what we get!
We are now logged in with Google! The hard part is done.
Authorized Personel Only
Remember way back when we created our pages controller? Well that secure
page is going to come in handy now.
First, add get 'pages/secure'
to config/routes.rb and then open up the pages controller. We are going to ensure that the user is logged in before viewing the secure page.
# 'app/controllers/pages_controller.rb'
class PagesController < ApplicationController
def home
end
def secure
unless user_signed_in?
redirect_to root_path, flash: { error: 'Please sign in first.' }
end
end
end
If we try to go to http://localhost:3000/pages/secure
without logging in first, we’ll get sent back to the home page with an error flashed. And there you have it!
Conclusion
There are so many reasons why logging in with Google is a great choice. Almost every user already has a Google account, you don’t have to deal with username/password resetting, and you don’t have to worry about securing passwords because you don’t have any. Thanks to the thriving Rails community, we have the tools to quickly authenticate with Google. Instead of a registration page on your next project, why don’t you try logging in with Google instead?
All example project code can be found on GitHub.