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.

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.

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?

!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

end

end

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 AuthenticationHelper

end

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

end

end

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

Popular posts from this blog

Now, this has to have a built-in somewhere in Scala , because it just seems too common. So, how to convert an Array to a List in Scala? Why do I need this? I needed to drop to Java for some functionality, which in this case returns an Array. I wanted to get that Array into a List to practice my functional programming skillz. **Update**: I figured out how to convert Arrays to Lists the Scala way. Turns out it's a piece of cake. val myList = List.fromArray(Array("one", "two", "three")) or val myList = Array("one","two","three").elements.toList The call to elements returns an Iterator , and from there you can convert to a List via toList . Nice. Because my first version wasn't actually tail recursive, what follows is a true tail recursive solution, if I were to implement this by hand. The above, built in mechanism is much better, though. object ArrayUtil { def toList[a](array: Array[a]): List[a] = { d

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

In which the virtues of automated mechanical arboreal pruning are extolled over quaint manual labor, as applied to web development build processes. The setup Ever notice how the primary bit of marketing for many traditional web programming libraries is their download size? Why is that? Check this out: Why does size matter so much for these libraries? Your first instinct is probably, "because the more bytes you shuttle across the wire, the slower the app starts up." Yes, this is true. I'd also say you're wrong. The primary reason that size matters for these libraries is because traditional web development has no intelligent or automated way to prune unused code so you can ship only the code that is used over the wire. The web is full of links, yet web dev has no linker The web development workflow is missing a linking step. A linker's job is to combine distinct project files into a single executable. A smart linker will only incl