OpenIdAuthentication in detail

I’m currently working on my own little project to fill photolog.at with something interesting and for this I decided to use OpenID only. Dr. Nic is a big fan of OpenID and of course I hearted his advice of allowing one account with many identities

The first OpenID plugin I tried was OpenIdAuthentication which would also support regular loging and I can say: it’s just perfect for me. The reason why I decided to allow OpenID login only is that the application will closely integrate into flickr, which is via Yahoo, one of the many OpenID providers. This way I save the obviously unnecessary authentication system, and a bunch of fields on the User model.

How OpenID works

The idea is very simple, you come to my website, type in your OpenID url, my website will forward you to your OpenID provider and there you might have to login. The OpenID provider then sends you back to my website and if nothing went wrong my website will know a few basic details about you, create an account if not existing yet and you are logged in. It’s very easy and quick, no need to type in data that you already stored at your provider.

In my webapplication most of those steps are automatically done by the OpenIdAuthentication plugin, but if you are sophisticated then you might want to change a few things from the example in the README. For instance, it doesn’t tell you anything about creating a new user, or – Dr. Nic’s favourite – how to assign multiple identities to one user. I’ve added all of those functions to my version of the SessionsController. But first a few lines from the models.

Models

The User model is quite simple, only login, name and email. All three will be gathered from the OpenID provider but you might want to add a edit form.

To comply with the mulitiple identities requirement I’ve added an Identity model which contains the identity_url (this quite replaces password and login in a normal user model) and user_id. A user has_many :identites and each Identity object belongs_to :user (just for the case you still have to look this up…).

The OpenIdAuthentication Plugin adds two models, OpenIdAuthentication::Association and OpenIdAuthentication::Nounce but we don’t touch these.

The SessionsController

Best is to follow OpenIdAuthentication’s README and then replace the SessionsController. Please read me comments and let me know what you think about it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class SessionsController < ApplicationController
  # sadly necessary
  skip_before_filter :verify_authenticity_token, :only => :create
  def create
    if using_open_id?
      # first difference: we explicitly pass the parameter. Much cleaner anyways than hiding it in the plugin...
      open_id_authentication(params[:openid_identifier] || params[:openid_url])
    end
  end

  protected
    def open_id_authentication(identity_url)
      # also possible for :required and :optional are %w( dob gender postcode country language timezone )
      # have a look here: http://openid.net/specs/openid-simple-registration-extension-1_0.html
      authenticate_with_open_id(identity_url,
          :required => [ :nickname, :email ],
          :optional => :fullname) do |result, identity_url, registration|
        if result.successful?
          # successful request, time to check wether the identity already exists
          identity = Identity.find_or_create_by_identity_url(identity_url)
          if identity.user.nil?
            # identity doesn't exist, but maybe the user? We use email as a unique key for this.
            if user = User.find_by_email(registration["email"])
              logger.debug("using user '%s'" % user)
              # assign identity to an existing user
              identity.user = user
              identity.save
            else
              logger.debug("creating new user '%s'" % registration["nickname"])
              # create a new user for the identity.
              identity.user = User.create!(:login => registration["nickname"],
                  :name => registration["fullname"], :email => registration["email"])
              identity.save
            end
          end
          # log the user in
          @current_user = identity.user
          successful_login
        else
          # oops. 
          failed_login result.message
        end
      end
    end
  
  private
    def successful_login
      session[:user_id] = @current_user.id
      redirect_to(root_url)
    end
  
    def failed_login(message)
      flash[:error] = message
      redirect_to(new_session_url)
    end
end

Leave a Reply