Ruby, Rails, OpenID, and Google Integration for the Busy Developer
We're busy developers, you and I, and we want to get back to building our awesome HTML5 app and not muck around too much with user sign in and registration. There are enough accounts out there, there's no reason why your users need to create a new account identity just for your system. Let them sign in with an existing account, your users will thank you!!
We're going to learn the bare minimum required to allow your users to use their Google Account to register and sign in to your Ruby on Rails 3 web app. I think you'll find that it's very easy to add OpenID support for Google Accounts, especially for Rails 3 web apps.
Assumptions:
- You are building a Ruby on Rails 3 web app.
- You do not yet have user authentication or registration for your app. A future article will show you how to add Google OpenID to your authlogic app, but this article is all about starting from scratch.
- You want your users to use their Google Accounts for their identity.
- You have a model named User which represents a registered user in your system.
(Once you have Google Account authentication via OpenID, you are more than ready to publish your web app in the Chrome Web Store. I've written a quick start guide for the Chrome Web Store which will help you get your app into the store in under 30 minutes.)
Step Zero: Add attributes to User
Although we are assuming you already have a User model, we need to add a
You can use a migration like:
This code also assumes you have
Step Zero: Add attributes to User
Although we are assuming you already have a User model, we need to add a
identifier_url
attribute. This attribute, which should be unique, stores the OpenID URL identifying the user.You can use a migration like:
class AddIdentifierUrlToUsers < ActiveRecord::Migration
def self.up
add_column :users, :identifier_url, :string
add_index :users, :identifier_url, :unique => true
end
def self.down
remove_index :users, :identifier_url
remove_column :users, :identifier_url
end
end
This code also assumes you have
email
, first_name
, and last_name
as attributes for your user, in addition to identifier_url
.Step One: Add the gems
You'll need both ruby-openid and rack-openid added to your Gemfile. It's as simple as:
gem "ruby-openid"gem "rack-openid"
And then, of course, run
Step Two: Tell Rails about Rack::OpenID
Open up
to the file of the file. Then, inside the
bundle install
which will pull down the gems and lock them into your project.Step Two: Tell Rails about Rack::OpenID
Open up
config/application.rb
and add
require 'rack/openid'
to the file of the file. Then, inside the
Application
you'll need to add
config.middleware.use 'Rack::OpenID'
Step Three: Create AuthenticationHelper
Create an
authentication_helper.rb
in the app/helpers
directory. This file should include:
module AuthenticationHelper def signed_in? !session[:user_id].nil? end def current_user @current_user ||= User.find(session[:user_id]) end def ensure_signed_in unless signed_in? session[:redirect_to] = request.request_uri redirect_to(new_session_path) end endend
Step Four: Tell your app about AuthenticationHelper
Open up
app/controllers/application_controller.rb
and add the line include AuthenticationHelper
class ApplicationController < ActionController::Base protect_from_forgery include AuthenticationHelperend
Step Five: Add the routes
Open up
config/routes.rb
and add the line:
resource :session
Step Six: Create the SessionsController
Create
app/controller/sessions_controller.rb
which should look like:
class SessionsController < ApplicationController skip_before_filter :verify_authenticity_token def new response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => "https://www.google.com/accounts/o8/id", :required => ["http://axschema.org/contact/email", "http://axschema.org/namePerson/first", "http://axschema.org/namePerson/last"], :return_to => session_url, :method => 'POST') head 401 end def create if openid = request.env[Rack::OpenID::RESPONSE] case openid.status when :success ax = OpenID::AX::FetchResponse.from_success_response(openid) user = User.where(:identifier_url => openid.display_identifier).first user ||= User.create!(:identifier_url => openid.display_identifier, :email => ax.get_single('http://axschema.org/contact/email'), :first_name => ax.get_single('http://axschema.org/namePerson/first'), :last_name => ax.get_single('http://axschema.org/namePerson/last')) session[:user_id] = user.id if user.first_name.blank? redirect_to(user_additional_info_path(user)) else redirect_to(session[:redirect_to] || root_path) end when :failure render :action => 'problem' end else redirect_to new_session_path end end def destroy session[:user_id] = nil redirect_to root_path endend
Note that the
create
method handles both sign in and registration.It's also very important to point out that even though this code asks for attributes like firstName and lastName, the OpenID provider might not return them to your app. So, you'll probably want to add a second step for new users to collect more information. This is accomplished with the
user_additional_info_path(user)
.Step Seven: Create an error page
Just in case it didn't work, you can display a helpful message for your user. Create a
app/views/sessions/problem.html.erb
<h1>DOH!</h1>
<p>Looks like your Google login didn't quite work. <%= link_to 'Try again?', new_session_path %></p>
Step Eight: Protect your pages
To ensure that only signed in users can access your resources, simply place
before_filter :ensure_signed_in
in any controller you wish to protect.For example:
class AdditionalInfosController < ApplicationController before_filter :ensure_signed_in def show @user = User.find(params[:user_id]) end def update @user = User.find(params[:user_id]) @user.update_attributes(params[:user]) redirect_to(session[:redirect_to] || root_path) end end
Summary
Congrats, you've gone from zero user registration and authentication to a full OpenID based Google Account based system. All of this code can be found in the Bracket Baby app on Github.