The No Shit Guide To Supporting OpenID In Your Applications

27 FEB

OpenID, with the superhuman effort of Mr Willison is taking the world by storm and I am amoungst the masses leaping onto the bandwagon. Simon has done an excellent post and screencast detailing the whys and what-fors of OpenID so I thought I’d have a bash at applying the same no-bullshit approach to the other side of the coin, supporting (or as the official terminology puts it consuming) OpenID in your applications. With this post I’m going to blast through the absolute essentials you need to get started so if you need more general background on OpenID check out Simon’s stuff first. The examples, as you might except will be using Ruby on Rails but all of the concepts are applicable across platforms. So, without further ado, grab yourself a beer and we’ll begin…

Overview of the authentication process

When consuming OpenID what you are trying to do is ask the user for their OpenID (which is a URL) then ascertain from their OpenID server that they actually own this OpenID. Once you know that they own the OpenID you can then wack it in the session (and use it as a really lightweight means of identifying users between visits) or key it in with your own application specific account data if you need more power. This article is going to take you up to and including verifying the user’s OpenID. What you do with it is left to your imagination.

On a more granular level the verification process breaks down into these steps:

Get the user to give you their OpenID URL . ‘Begin’ the verification process whereby your OpenID library of choice will work out the users OpenID server and, if successful, provide you with a redirect URL . Redirect the user to the given redirect URL . You specify a return URL within this URL . The user goes to their OpenID server, logs in and authorises your site’s verification request and is then redirected back to your return URL . Your server ‘completes’ the verification request and, if successful, confirms that this user owns this OpenID. The end.

So that’s essentially it. Some of the details of the transactions between your server, the user’s delegates and the OpenID are pretty complex but fortunately for us there are lots of good libraries for most platforms that mean you don’t need to bugger about with the crypotography and stuff. Woo hoo. For these examples we are going to use the ruby-openid gem but you can choose your own. Also note that East Media have a OpenID Consumer plugin for Rails that wraps even more detail with some generators but it’s good to understand the concepts before you let something write your code for you.

Get your library sorted

That’s easy. For us Rubyists its:

$ sudo gem install ruby-openid -y

Create your OpenID consuming controller

We are going to try to be as RESTy as possible here so we’ll create a singleton resource called openid. In routes.rb:

map.resource :openid, :member => { :complete => :get }

Then we’ll set up a simple controller. Firstly, we’ll need to require the ruby-openid gem here. We are also going to need a method that gives us an OpenID consumer object which is the single most complex part of this whole thing (and it isn’t complex). First, here’s the skeleton:

require_gem 'ruby-openid' class OpenidController < ApplicationController def new # TODO: show a form requesting the user's OpenID end def create # TODO: begin the OpenID verification process end def complete # TODO: omplete the OpenID verification process end protected def openid_consumer @openid_consumer ||= OpenID::Consumer.new(session, OpenID::FilesystemStore.new("#{RAILS_ROOT}/tmp/openid")) end end

The OpenID::Consumer constructor takes two arguments, the first one should be a hash like object that holds session data. That’s always going to be session for Rails. The second one takes a file store object which is used to store state information for the verification process. There’s lots (including an ActiveRecord store) but for many apps the filesystem store is fine.

Getting the user’s OpenID

The new action just needs to show a simple form posting the OpenID to the create action:

<% form_tag openid_path do %> <%= text_field_tag 'openid_url' %> <%= submit_tag 'Login' %> <% end %>

Note that it’s convention to call the field openid_url so browsers will autocomplete nicely. They also recommend that you embed the OpenID logo in the form field. Get the logo then try some CSS like this:

#openid_url { background: url(/images/login-bg.gif) no-repeat #FFF 5px; padding-left: 25px; }

Beginning the verification

The create action is going to be responsible for kicking off the process:

def create openid_url = params[:openid_url] response = openid_consumer.begin openid_url if response.status == OpenID::SUCCESS redirect_url = response.redirect_url(home_url, complete_openid_url) redirect_to redirect_url return end flash[:error] = "Couldn't find an OpenID for that URL" render :action => :new end

We simply get the OpenID and pass it to the begin method of our consumer object to get a response. We then handle the status of the response which can have a number of states. For this super simple example we are just going to look for success but in a production app you’ll need to handle error states more usefully.

If the response was successful we call redirect_url passing the trust root and the return URL. The return URL is simply our complete action. The trust root is normally the homepage URL of your site. We then redirect the user to the resulting URL where the user logs in to their OpenID server, authorises your verification request and is (normally) redirected to return URL you provided.

Completing the verification

When the user is redirected back to your application the server will append information about the response in the query string which the OpenID library will unpack:

def complete response = openid_consumer.complete params if response.status == OpenID::SUCCESS session[:openid] = response.identity_url # the user is now logged in with OpenID! redirect_to home_url return end flash[:error] = 'Could not log on with your OpenID' redirect_to new_openid_url end

After passing the params hash containing all the info that the OpenID server sent us to the complete method we are given a response status to handle. Again for production apps more states should be handled but here, if the complete was successful we have completed the process. Here we just store the identity_url given in the session but at this point we could also do something like:

session[:user] = User.find_by_openid_url(response.identity_url)

Which would grab the users local account data based on the OpenID. Easy as pasty. However, there’s a few more bits and bobs you might want to know about.

Simple Registration Extension (SReg)

SReg is a basic means by which you can request additional information about the user from their OpenID server which you might normally use to prefill account details or other form fields. The information you can request access to is in the spec but there’s not much there at the moment. It’s still kind of useful. To request this information you need to add parameters to the redirect URL which is of course handled for you by your library. Revisiting the create action, we just add a call to add_extension_arg:

def create openid_url = params[:openid_url] response = openid_consumer.begin openid_url if response.status == OpenID::SUCCESS response.add_extension_arg('sreg','required','email') # <== here... response.add_extension_arg('sreg','optional','nickname,gender') # <== ...and here redirect_url = response.redirect_url(home_url, complete_openid_url) redirect_to redirect_url return end flash[:error] = "Couldn't find an OpenID for that URL" render :action => :new end

Then in the complete action, extract the returned information:

def complete response = openid_consumer.complete params if response.status == OpenID::SUCCESS session[:openid] = response.identity_url # the user is now logged in with OpenID! @registration_info = response.extension_response('sreg') # <= { 'name' => 'Dan Webb', etc... } redirect_to home_url return end flash[:error] = 'Could not log on with your OpenID' redirect_to new_openid_url end

Immediate mode

Immediate mode allows you to attempt to verify the user without them leaving your site at all. This is normally possible if, during the first time you attempt to verify a user, they choose to always allow you to verify them and offers a slightly more streamlined login experience.

To implement this we first pass an extra argument to redirect_url:

def create openid_url = params[:openid_url] response = openid_consumer.begin openid_url if response.status == OpenID::SUCCESS redirect_url = response.redirect_url(home_url, complete_openid_url, true) # <== here redirect_to redirect_url return end flash[:error] = "Couldn't find an OpenID for that URL" render :action => :new end

Then ensure that our complete action handles the OpenID::SETUP_NEEDED status by redirecting them to the OpenID server’s setup page:

def complete response = openid_consumer.complete params case response.status when OpenID::SUCCESS session[:openid] = response.identity_url redirect_to home_url return when OpenID::SETUP_NEEDED redirect_to response.setup_url # <== here! return end flash[:error] = 'Could not log on with your OpenID' redirect_to new_openid_url end

Fin

So that’s it. All you need to know to start getting OpenID going in your web applications. You’ve got no excuse now. In fact, neither have I…

Any corrections, questions or further wisdom are very welcome. I’m no expert on OpenID, I’ve literally just picked all this up from reading API docs, specs and source code and thought it would be good to share. let me know what you think.