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.

  • 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 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

  def self.down
    remove_index :users, :identifier_url
    remove_column :users, :identifier_url

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 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?
  def current_user
    @current_user ||= User.find(session[:user_id])
  def ensure_signed_in
    unless signed_in?
      session[:redirect_to] = request.request_uri

Step Four: Tell your app about AuthenticationHelper
Open up app/controllers/application_controller.rb and add the line include AuthenticationHelper

class ApplicationController < ActionController::Base
  include AuthenticationHelper

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 => "",
        :required => ["",
        :return_to => session_url,
        :method => 'POST')
    head 401
  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(''),
                              :first_name => ax.get_single(''),
                              :last_name => ax.get_single(''))
        session[:user_id] =
        if user.first_name.blank?
          redirect_to(session[:redirect_to] || root_path)
      when :failure
        render :action => 'problem'
      redirect_to new_session_path
  def destroy
    session[:user_id] = nil
    redirect_to root_path

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


<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])
  def update
    @user = User.find(params[:user_id])
    redirect_to(session[:redirect_to] || root_path)

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.

Popular posts from this blog

  • Sponsor:  Register today for  New Game, the conference for HTML5 game developers . Learn from Mozilla, Opera, Google, Spil, Bocoup, Mandreel, Subsonic, Gamesalad, EA, Zynga, and others at this intimate and technically rich conference. Join us for two days of content from developers building HTML5 games today. Nov 1-2, 2011 in San Francisco.  Register now ! This is the second article in a Box2D series, following the Box2D Orientation article. The Box2DWeb port of Box2D contains a nice example to show off the basics of integrating physics simulations into your web app. This post will provide a walkthrough of the example, explaining the high level concepts and code. First, let's see the example in action. The code for the above is open source and available on GitHub. It was adapted from Box2DWeb's example . Animating Before we look at Box2D, it's important to understand how the above simulation is animated. You might think setInterval or setTimeout is
    Keep reading
  • In which I port a snazzy little JavaScript audio web app to Dart , discover a bug, and high-five type annotations. Here's what I learned. [As it says in the header of this blog, I'm a seasoned Dart developer. However, I certainly don't write Dart every day (I wish!). Don't interpret this post as "Hi, I'm new to Dart". Instead, interpret this post as "I'm applying what I've been documenting."] This post analyzes two versions of the same app, both the original (JavaScript) version and the Dart version. The original version is a proxy for any small JavaScript app, there's nothing particularly special about the original version, which is why it made for a good example. This post discusses the differences between the two implementations: file organization, dependencies and modules, shims, classes, type annotations, event handling, calling multiple methods, asynchronous programming, animation, and interop with JavaScript libraries. F
    Keep reading
  • Angular and Polymer, sitting in a DOM tree, B-i-n-d-i-n-g. First comes components, Then comes elements, Then comes the interop with the node dot bind. Angular , a super heroic MVC framework, and Polymer , polyfills and enhancements for custom elements built on top of Web Components, can live harmoniously in the same app. This post shows you how to connect Angular-controlled components to Polymer-controlled elements via data binding. And we do it all in Dart . Angular and Polymer I get asked "Should I use Angular or Polymer?" a lot. My answer is, "Yes". That is, both libraries have distinct strengths, and you can use both in the same app. Polymer excels at creating encapsulated custom elements. You can use those custom elements in any web app or web page, regardless if that app is built with Angular, Ember, etc. Angular excels at application engineering, with dependency injection, end-to-end testability, routing, and services. Here are som
    Keep reading