Posts tagged OpenID

More notes from a Rails 3.0pre upgrade

This is a follow-on from my piece on how I got the (development version of) Catapult Magazine up and running with Rails 3.0pre. If you haven’t already done so, I’d recommend you read that first.

Catapult makes use of the permalink_fu plugin which fails in Rails 3. It fails because of a reliance on the evaluate_attribute_method method which no longer exists in version 3. I’ve temporarily worked around that by replacing it with class_eval, but lately I’ve been using friendly_id a lot more and I suspect I’ll be focussing on porting to that if it works cleanly in Rails 3.

***

A dependency I’ve long wanted to part with is has_many_polymorphs. I’m using it to manage the categorisation of issues and posts but all that’s ever really saved me is one table/model, so perhaps now is the time to clean up the models and lose that dependency.

***

open_id_authentication uses a config.to_prepare block to include itself in ActionController::Base but that’ll work just as well if I add

include OpenIdAuthentication

to my ApplicationController, so that’s what I’ve done. I suspect a number of other changes will be needed (there’s a reference to config.gem in there) but so far, so good.

***

The key issue I’ve run into is that the init.rb files in my gem plugins aren’t run automatically, meaning that in addition, say, to declaring a model has_attached_file (for paperclip) I also need to require ‘paperclip’. That’s not totally unreasonable, but in a complex app it could be quite time consuming and begins to move away from the sense that a plugin/gem is a self-contained unit.

Looking through the new rails source I found the method that loads the init.rb files for plugins installed in vendor/plugins. Rails::Plugin::Vendored (in railties/lib/rails/plugin.rb) contains:

initializer :load_init_rb, :before => :load_application_initializers do |app|
  file   = "#{@path}/init.rb"
  config = app.config
  eval File.read(file), binding, file if File.file?(file)
end

but that only looks through those, not gem plugins. It wouldn’t be too hard to add some code in the initializer to glob the relevant folder and load those files but I’ve not had time yet to decide if that’s my best way forward.

Why we’re not quite ready for everyone to build their own social networking site

Whether or not you should build your own social networking site and/or make use of sites like facebook is currently a hot topic within the not-for-profit web developer/consultant world. The launch of sites like Amnesty International’s “unsubscribed”, which bears many hallmarks of a social networking site, combined with growing attention for facebook campaigns and tools like SuperBadger bring the options and potential into clear focus. Elizabeth Dunn’s post last month “social networks, walled gardens, and decision trees” makes a compelling argument that non-profits should be focussing on these questions now even if they’re not key for their current audience: sooner or later they will be and you don’t want to be playing catchup.

There are certainly many advantages to having your own social networking site. A facebook or myspace presence may attract attention, but the data you’ll be able to gather about your supporters and the potential for inserting your own branding are limited. Set up your own social network and you have full flexibility to integrate that data with your supporter databases, and to tune the site to your very specific requirements. But not only are they a significant investment of resources, there’s also a significant cost of time for supporters who will need to sign up for yet another site, identify their friends once again and give their attention to even more online data. Online campaigning’s great strength so far has been its low barrier to entry; for cause-specific social networks to really make sense their barriers need to fall.

That desire is definitely not unique to non-profits. As Brian Suda’s “Portable Social Networks: Take Your Friends With You” highlights, finding ways to let users move their data from site to site is a hot topic across the web development world. Sites like traveller network dopplr have succeeded in part by letting their (so far highly tech savvy) users import data from other social networks such as twitter. The real breakthrough will be when the mass-networks of the moment (as facebook is today) become similarly open. That won’t make it easy, but it will mean that there is suddenly a huge audience who can become fully signed up for your site with just a couple of clicks.

Right now most efforts hinge on a set of emerging standards, two of which will be familiar to anyone who’s been reading this blog for some time but which bear some more attention. There’s plenty of information around the web on these so I’ll just touch on them briefly:

Microformats are a way of taking plain old HTML and, by following some conventions, adding meaning to the content that could be understood by machines. While a human might be able to look at some information and infer that it is describing an event, machines aren’t so good at that, so we need standardised ways to say “this is describing an event, that bit is the date,” or “this link is to the homepage of my brother.” There were already ways to do that, but it involved adding extra things to your website. With microformats, so long as you (or your content management system) follow some simple conventions when creating a page, a machine can get the content out of the page as easily as a human reader can. That lets us identify friends/contact lists in a portable way, among other useful functions.

OpenID is a way to get rid of the frustration of having to create a username and password for every new site you visit. Instead when you visit a new site that you want to sign up for, you enter a URL that is your OpenID and that site will check with a central system (which may require you to log in) whether you do in fact own that OpenID. Rather than having to remember dozens of usernames and passwords, you’ll probably just have to remember one URL, one username, and one password. And because you’re using that same OpenID for lots of sites, when you sign up for a new site you can be identified on others. So if you sign up for a new site I’ve created using the same OpenID you use on livejournal my software can identify you on livejournal and import your posts from there. So there’s less work for you to tell that new site you’re creating a profile on how to find your blog and to import the posts, and an easy way to make your profile active and informative.

OAuth is the newest of the standards and goes hand-in-hand with OpenID. It allows you to grant one web application permission to access certain parts of your data in another application without giving your usernames and passwords all over the places. If you’ve ever used a site that redirected you to flickr to get your permission to do something with your photos, you’ve seen something like it. If OAuth is widely adopted, no longer will you have to give every social networking site your webmail username and password in order to have your address book checked for other users of the site, simply trusting that they won’t abuse it. Instead, it becomes easy to give a one-off permission while keeping your details as secret as they should be.

What these pieces add up to is a significant reduction in the psychological hurdles that might prevent your supporters from joining your new social network. Instead of pouring hours into their friendster profile only to find everyone has moved to myspace, and so not signing up for your site because they’d have to go through the whole process all over again, they can sign up with their OpenID, perhaps grant you a few permissions with OAuth (knowing that they’re not handing over the keys to their email, online photos, or whatever) and be signed up with an already complete profile.

In other words, persuading people to sign up and build their profiles is no longer the issue and you can focus on providing them with compelling reasons to keep coming back.

So if you’re wondering whether now is the time to start building out your campaign’s fancy new social networking site, it just may be, but chances are a few months from now will be a better time. As OpenID and OAuth become better established—and maybe even get adopted by some of the big players—your life is going to be easier and provided you pick web developers who’ve been keeping up you’ll be able to focus on your campaign strategy rather than coaxing visitors to spend another few hours re-entering the same old details.

Jeremiah Owyang on Facebook Strategies and Beyond

A number of people have been linking to Jeremiah Owyang’s presentation at the Web Community Forum (I think I found it via Beth Kanter). It’s a good overview of the pros, cons and options for using facebook to promote a cause, campaign or brand, and well worth some time if that’s your focus.

There are two pieces from his presentation that I wanted to pull out. The first appears to be a recurring theme in his work on web strategy, centered on the acronym POST. That breaks down into:

People
assess you customers’ Social Technographics profile
Objectives
Decide what you want to accomplish
Strategy
Plan for how relationships with customers will change
Technology
Decide which social technologies to use

The third and fourth of those are particularly good to see. Not only do you need a strategy, but you need to recognise that your relationship with your customers will change when you engage them in a new medium. Too much of the focus on using Web 2.0 to promote a cause has focussed on other ways of putting across a message. It’s simply a translation of “if you’re not everywhere, you’re nowhere” from offline to online media. But part of the promise of the web lies in the fact that it’s no longer your job to get yourself everywhere. Instead you need to build stronger relationships with key stakeholders and they’ll then spread the word if they want to. You just need to look at facebook for evidence — you can put all the time in the world into building a profile, group, application or Page, but unless people want to friend you, use your app or call themselves a fan, your message won’t be seen.

Looking to the future it was also good to see the reminder “Don’t limit to Facebook,” summarised with the bullets:

  • Brands should not limit strategy to Facebook Alone
  • Prepare for The Distributed Web
  • Understand OpenSocial
  • Understand the Aggregation of Social Graph
  • Tools come and go, what sustains is a strategy

Beth has some good points about building on that last one, but I was just glad to see a perspective looking beyond the current dominance of facebook. If the announcements in the web world over the past couple of months about technologies like OpenID, OAuth and OpenSocial are anything to go by, 2008 is going to bring some significant changes in the world of ’social networking sites.’ (I’ve had a draft on that topic sitting around for far too long, hopefully I’ll get it finished and posted this week!)

A Ruby on Rails OpenID Server

Since I wrote my first piece on extending a rails app to accept OpenID quite a few other tutorials and an official plugin have appeared to make that process easier. OpenID is quickly becoming quite mainstream, at least amongst developers, and that is very good news.

It’s becoming so mainstream in fact, that recently I’ve been asked to implement an OpenID server on top of an existing user database so that those users can have an easy single-sign-on option across a range of sites. Writing the server side piece is not quite so straightforward and there’s not much documentation yet. A few sample servers are available but the rails examples don’t run cleanly on the latest gems, so while I took some code from them it made most sense to start from scratch. Over the past couple of days I’ve hacked together something that works for me and even though it could still do with some polish a few notes follow. Please do use the comments to correct anything I may have gotten wrong or skipped over.

Firstly, a word on the process. The OpenID process works along the lines of:

  1. The client sends a request to the server to establish communication, shared keys, etc.
  2. Having established the keys, the client then redirects the end-user to the server to log in
  3. The server logs the end-user in however it wishes
  4. The server redirects the end-user back to the client with a query string indicating success or failure
  5. The client then confirms the success of the request with the server

I was starting from a very simple rails app that provided a Users resource and a Session resource. It’s really little more than a skeleton app with restful_authentication installed and configured. Users can log in and anyone can see a user’s page, and that’s all there is to it. I also had the ruby-openid gem installed and used a fair bit of code from the sample apps supplied with the gem.

Preparing The Existing Site

The first thing you’ll want to do is make sure that the HTML pages for your user resources have the correct headers. Chances are you will want users to be able to use their page (eg. http://yourapp.com/users/jystewart) as their OpenID but that won’t actually be where the login takes place, so you’ll want to put something like:

<link rel="openid.server" href="<%= session_url %>" />
</link><link rel="openid2.provider" href="<%= session_url %>" />
</link>

in the header block of your user page HTML. From that the OpenID client will know to redirect users to your sessions_url for authentication.

Preparing The Models

To my database that so far held the user details, I added four tables:

create_table :nonces do |t|
  t.string :nonce
  t.integer :created
  t.timestamps 
end
 
create_table :associations do |t|
  t.binary :server_url, :secret
  t.string :handle, :assoc_type
  t.integer :issued, :lifetime
  t.timestamps 
end
 
create_table :settings do |t|
  t.string :setting
  t.binary :value
  t.timestamps 
end
 
create_table :trusts do |t|
  t.integer :user_id
  t.string :trust_root
  t.timestamps 
end

The first three hold the various details we need for the negotiations with the client, and the fourth stores the details of any sites a given user has said they want to always trust so that we don’t need to confirm those details every time.

We also need an implementation of the OpenID::Store class for the OpenID library we’re using. I simply copied the one from the sample rails app distributed with the gem. With the models and that interface in place we can move on to managing the requests.

Distinguishing OpenID Requests

I decided to use a Mime::Type alias to distinguish between requests made directly in my app and those coming through OpenID. To do that I added:

Mime::Type.register_alias "text/html", :openid

in RAILS_ROOT/config/initializers/mime_types.rb and added the following code in my controllers:

before_filter :check_for_openid_request
 
private
  def openid_server # :nodoc:
    @openid_server ||= OpenID::Server::Server.new(ActiveRecordOpenIDStore.new)
  end
 
  def openid_request
    @openid_request ||= openid_server.decode_request(
      openid_params.delete_if{ |key, val|
        key == 'openid.session_type' && val == ''})
  end
 
  def check_for_openid_request
    @openid_params = session[:openid_params] if session[:openid_params]
 
    if openid_request.is_a?(OpenID::Server::CheckIDRequest)
      session[:openid_params] = @openid_params
      request.format = :openid 
    end
  end

Because there are going to be several steps to our process during which we want to be able to hang on to the OpenID request details, I chose to store them in the session

Handling The Requests

Having defined our Mime::Type alias, we can now use openid as a type in respond_to blocks. The first step then is to change our SessionsController#create action from:

def create
  self.current_user = User.authenticate(params[:login], params[:password])
  if logged_in?
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { 
        :value => self.current_user.remember_token, 
        :expires => self.current_user.remember_token_expires_at }
    end
 
    flash[:notice] = "Logged in successfully"
    redirect_back_or_default(user_path(current_user))
  else
    render :action => 'new'
  end
end

to:

def create
  self.current_user = User.authenticate(params[:login], params[:password])
  if logged_in?
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { 
        :value => self.current_user.remember_token, 
        :expires => self.current_user.remember_token_expires_at }
    end
 
    respond_to do |wants|
      wants.html { 
        flash[:notice] = "Logged in successfully"
        redirect_back_or_default(user_path(current_user))
      }
      wants.openid { 
        if @trust = current_user.trusts.find_by_trust_root(openid_params['openid.trust_root'])
          successful_openid_login 
        else
          redirect_to new_trust_url
        end
      }
    end
  else
    render :action => 'new'
  end
end

This checks for a successful login and if it’s an OpenID request then checks to see if we already trust that site. If we do, we call the ’successful_openid_login’ method and if not, we redirect the user to ask if they want to trust the client site. The successful_openid_login method looks like:

def successful_openid_login
  session[:openid_params] = nil
  @resp = openid_request.answer(true)
  @resp.add_field(nil, 'is_valid', 'true')
  add_sreg
  render_openid_response
end
 
def render_openid_response
  response.headers['Content-Type'] = 'charset=utf-8'
  @resp = openid_server.encode_response(@resp)
 
  case @resp.code
    when OpenID::Server::HTTP_OK
      render :text => @resp.body, :status => 200
    when OpenID::Server::HTTP_REDIRECT
      redirect_to @resp.redirect_url
    else
      render :text => @resp.body, :status => 400
  end   
end

And is almost entirely taken from the code supplied with the gem. I ran into some trouble with the ‘is_valid’ field not being added to the responses but being needed by my clients, so added the line

@resp.add_field(nil, 'is_valid', 'true')

The TrustsController implementation is quite straightforward and I’ll not go into details here, but will include it in the zip file you can download at the end of this entry.

This code will handle most of the process, but we have one remaining query to handle, and that is the final check_authentication request that is used to confirm everything fell into place. The actual request will come as a POST to your /session path so you will want to be ready to handle it there. I decided to implement a before_filter to intercept it and handle it separately rather than clutter my create method still further:

before_filter :handle_openid_check_auth_request
 
def handle_openid_check_auth_request
  if openid_params['openid.mode'] == "check_authentication"
    signatory = OpenID::Server::Signatory.new(ActiveRecordOpenIDStore.new)
    @resp = openid_request.answer(signatory)
    @resp.add_field(nil, 'is_valid', 'true')
    render_openid_response
  end
end

Once I had the code working acceptably I pulled it out into a separate module in the ‘lib’ folder. As the code matures it would probably be a good candidate for a rails plugin, but I’m not going to have time to put that together on my own for a while. If anyone wants to collaborate on that, get in touch in the comments (or by email) and maybe we can make it happen.

All the code above along with various other utility methods can be found in this zip file.

OpenID for a Ruby on Rails app

The buzz about OpenID keeps building, and with the announcement that all AOL/AIM users now automatically have OpenIDs it doesn’t look set to slow down any time soon. For those who’re not familiar with the concept, OpenID is a distributed single sign-on system that lets you set up one account and then log in to any OpenID compliant site using that account. For a better introduction, check out this screencast from Simon Willison.

There’s already been a fair bit of work done to allow ruby and rails apps to make use of open ID. There’s a gem, ported from the python library which provides APis for consumers and servers. And there’s a rails plugin that provides some generators for creating the models and controllers you need to interact with the gem.

I’ve been playing with the plugin on and off for a couple of months, but yesterday finally got round to integrating it properly with an application that I’ve been gradually developing. The app uses restful_authentication to handle its login needs, and I had to make a few customizations to get everything working as I want.

The process I adopted was to say that people can either create a standard account with a regular username and password, or they can sign in with their OpenID and then create an account in my system that will be authenticated against that OpenID. So the first step was simply to adapt the sign up form to include some explanation, and an OpenID sign in form.

I then had to update the controller that the plugin had created for me. By default it responds to a successful Open ID sign in by presenting a user with a page presenting the details of their Open ID identity, but I wanted to take it a step further.

I replaced the default code in the controller with:

when OpenID::SUCCESS
  user = User.find_by_openid_url(open_id_response.identity_url)
  if user.nil?
    session[:openid] = open_id_response.identity_url
    flash[:attributes] = open_id_fields
    flash[:notice] = 'Thank you, we have confirmed your identity. Please proceed to create your account'
    redirect_to new_user_url and return
  else
    self.current_user = user
    flash[:notice] = 'Thank you. You are now logged in'
    redirect_to "/" and return
  end

That checks whether there’s already an account for that open id. If there is, we consider the user logged in and take them to the homepage. If not, we store their openid details in the session and send them to complete the sign up.

By also adding:

params[:user][:openid_url] = session[:openid] if session[:openid]

in UsersController#create and making a few simple changes to the default User model supplied with restful_authentication so that it doesn’t require a login/password when it has an OpenID, I have an app which can take full advantage of OpenID, and my users have one less set of login details to remember.

Given the popularity of acts_as_authenticated and restful_authentication, it would be good to see a plugin which does all the work of integrating OpenID with those libraries. It shouldn’t be much work; if time allows, I may have a go, but if anyone beats me to it please let me know!