Agile Web Development

Build it. Launch it. Love it.

Open Id Authentication

Provides a thin wrapper around the excellent ruby-openid gem from JanRain. Be sure to install that first:

  gem install ruby-openid

To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb from that gem.

Prerequisites

OpenID authentication uses the session, so be sure that you haven’t turned that off. It also relies on tmp/openids being present in RAILS_ROOT. The install.rb should install that automatically as you get the plugin, but if not, be sure to do that yourself.

This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations. If you’re using RESTful authentication, you’ll need to explicitly allow for this in your routes.rb.

Example

This example is just to meant to demonstrate how you could use OpenID authentication. You’ll might well want to add salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point, not a destination.

config/routes.rb

  map.open_id_complete 'session', :controller => "session", :action => "create", :requirements => { :method => :get }
  map.resource :session

app/controllers/session_controller.rb

  class SessionController < ApplicationController
    def create
      if open_id?(params[:name])
        open_id_authentication(params[:name])
      else
        password_authentication(params[:name], params[:password])
      end
    end

    protected
      def password_authentication(name, password)
        if @current_user = @account.users.find_by_name_and_password(params[:name], params[:password])
          successful_login
        else
          failed_login "Sorry, that username/password doesn't work"
        end
      end

      def open_id_authentication(identity_url)
        authenticate_with_open_id(identity_url) do |status, identity_url|
          case status
          when :missing
            failed_login "Sorry, the OpenID server couldn't be found"
          when :canceled
            failed_login "OpenID verification was canceled"
          when :failed
            failed_login "Sorry, the OpenID verification failed"
          when :successful
            if @current_user = @account.users.find_by_identity_url(identity_url)
              successful_login
            else
              failed_login "Sorry, no user by that identity URL exists"
            end
          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

      # Set #root_url if your root url has a different named route.
      #
      #   map.home '', :controller => ..., :action => ...
      #
      # Otherwise, name the route 'root' and leave this method out.
      def root_url
        home_url
      end
  end

Simple Registration OpenID Extension

Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension

You can support it in your app by changing #open_id_authentication

      def open_id_authentication(identity_url)
        # Pass optional :required and :optional keys to specify what sreg fields you want.
        # Be sure to yield registration, a third argument in the #authenticate_with_open_id block.
        authenticate_with_open_id(identity_url, :required => [:nickname, :email], :optional => :fullname) do |status, identity_url, registration|
          case status
          when :missing
            failed_login "Sorry, the OpenID server couldn't be found"
          when :canceled
            failed_login "OpenID verification was canceled"
          when :failed
            failed_login "Sorry, the OpenID verification failed"
          when :successful
            if @current_user = @account.users.find_by_identity_url(identity_url)
              # registration is a hash containing the valid sreg keys given above
              # use this to map them to fields of your user model
              {'login=' => 'nickname', 'email=' => 'email', 'display_name=' => 'fullname'}.each do |attr, reg|
                current_user.send(attr, registration[reg]) unless registration[reg].blank?
              end
              unless current_user.save
                flash[:error] = "Error saving the fields from your OpenID profile: #{current_user.errors.full_messages.to_sentence}"
              end
              successful_login
            else
              failed_login "Sorry, no user by that identity URL exists"
            end
          end
        end
      end

Copyright © 2007 David Heinemeier Hansson, released under the MIT license

Vitals

Home http://github.com/rails/open_id_authentication/tree/master
Repository git://github.com/rails/open_id_authentication.git
License Rails' (MIT)
Rating (53 votes)
Owner David Heinemeier Hansson
Created 27 February 2007

Comments

  • Avatar
    jan
    7 April 2007

    I'm sure I'm missing something here, but the example didn't work that much for me. POST parameters are not provided after the authentication, so I couldn't just use the same params[:name] again. Also I needed to change "case status" to status.instancevariableget(:@code)

  • Avatar
    10 March 2007

    I have noticed that nothing is mentioned on this page about making it so the user can be logged in automatically in the future. There is no 'remember' option.

    I am trying to implement OpenID on a new app of mine, and require a 'remember me' option.

    I am thinking that the best way to go is to set a cookie that is the OpenID when someone logs in. Then when they return to the page after their session has expired, initiate a new openid request and try to do it immediately.

    However, this check for whether or not the cookie is set needs to be done in 'application.rb', so it can be available to all controllers, not just the logging in controller. But, then all controllers can no longer access the 'openidauthentication' method from SessionController. This is a problem. Currently, my solution is to have it redirect to the 'create' action in SessionController.

    I am looking for a more elegant solution. I would prefer to not to have to do the extra redirect to the 'create' action in SessionController and just start the openid request right there. BUT the OpenID code shouldn't go in 'application.rb'.

    One solution would be to just not reinitiate an OpenID login from the cookie and instead store some sort of salted hash, but it seems to me that is would be most secure to verify that this site is still allowed to use that particular OpenID. Because of the 'immediate' option with OpenID, this shouldn't cause too much of a delay for accessing the site.

    Any thoughts?

  • James Greenfield
    21 April 2007

    There's a bug in the case statement. Ruby calls the === operator on the objects in the when clause, passing the object at the head of the case statement as a parameter. The === operator has been overloaded on the OpenIdAuthentication::Result class to allow for case statements using symbols like that used in the example. But the === method is never called on this class. To fix it, expose the @code member on Result (attr_accessor :code) and change the case statement from

    case status when :missing ... end

    to

    case status.code when :missing ... end

  • Avatar
    14 May 2007

    A full working sample application with restfulauthentication + openid_authenication is available at http://www.bencurtis.com/archives/2007/05/openid-sample-application/

  • Avatar
    13 June 2007

    Does the ":requirements => { :method => :get }" bit really work out of the box in any recent version of Rails? Doesn't break anything, but neither does it seem to actually limit the request methods you can use.

    Something similar <http://dev.rubyonrails.org/changeset/4209> was built-in once, but I don't think it is anymore -- to my knowledge, you need the Request Routing plugin <http://agilewebdevelopment.com/plugins/request_routing>.

    Unless I'm mistaken, the README should be updated to reflect this.

  • Avatar
    14 June 2007

    After researching it further, it seems routing by method is possible, but you need ":conditions => { :method => :get }" rather than ":requirements ..." as the README claims.

  • Avatar
    17 June 2007

    There is a bug in the return URL generation in the plugin.

    def open_id_redirect_url(open_id_response)
      open_id_response.redirect_url(
        request.protocol + request.host_with_port + &quot;/&quot;,
        open_id_response.return_to(&quot;#{request.url}?open_id_complete=1&quot;)
      )     
    end
    

    should take into account that request.uri (which itself isn't in the version of Rails I'm using) might already include parameters. If it does, Rails attaches "?openidcomplete=1" to the last parameter, like so: Parameters: {"format"=>"html?openidcomplete=1", "openid.mode"=>"idres", "openid.returnto"=> ... }

    My suggestion is the following: def openidredirecturl(openid_response) return_url = request.url returnurl << returnurl.include?('?') ? '&' : '?' returnurl << openid_complete=1

    open_id_response.redirect_url(
      request.protocol + request.host_with_port + &quot;/&quot;,
      open_id_response.return_to(return_url)
    )
    

    end

  • Avatar
    Randy
    21 July 2007

    I've run into a wall with this. I have followed the tutorial at http://www.bencurtis.com/archives/2007/03/rails-openid-and-acts-as-authenticated/ and it works perfectly in development mode. If the account doesn't exist, my app goes out and checks that the openid account authorizes the login and then creates a new account with only the identity_url field filled in. If the account already exists, it simply logs the person in. Flawless and beautiful. Love it!

    As soon as I switch to production mode, it's a less enjoyable ride. First let me say that I made sure my databases were identical and I had full access to both. I even entered myappname_production into the development area in database.yml and it works perfectly. Database ok? check! OSX: 10.4.10 Rails: 1.2.3 Ruby: 1.8.6 ruby-openid: 1.1.4 ruby-yadis: 0.3.4 OpenIdAuthentication plugin: can't find a version # actsasauthenticated: can't find version # changelog stops at 1 aug 2006

    Here are the lines from my development log file that are missing from my production log: OpenIdAuthentication::Nonce Columns (0.004343) SHOW FIELDS FROM openidauthentication_nonces OpenIdAuthentication::Nonce Load (0.004212) SELECT * FROM openidauthenticationnonces WHERE (openidauthenticationnonces. 'nonce' = '8noyAML7') LIMIT 1 SQL (0.000420) BEGIN SQL (0.000843) INSERT INTO openidauthentication_nonces ('created', 'nonce') VALUES(1184979407, '8noyAML7') SQL (0.001165) COMMIT OpenIdAuthentication::Association Columns (0.005295) SHOW FIELDS FROM openidauthentication_associations OpenIdAuthentication::Association Load (0.004116) SELECT * FROM openidauthenticationassociations WHERE (openidauthenticationassociations.'server_url' = 'http://www.myopenid.com/server'

    There are more of the same after a bit, all dealing with the OpenIdAuthentication routines. Why would the OpenIdAuthentication code fire when in development mode and not in production mode? I'm lost as to where to look as I've never experienced a plugin only working in one mode and not the other.

  • Avatar
    snowmaninthesun
    23 June 2008

    I have installed "sudo gem install ruby-openid" successfully then installed the plugin.

    "script/plugin install http://svn.rubyonrails.org/rails/plugins/openidauthentication/"

    the error i get is "no such file to load -- openid/extensions/sreg" and in the console "Install the ruby-openid gem to enable OpenID support"

    i'm using rails 2.0.2 and open-id 2.0.4.

    in script/console "puts OpenID::VERSION" yeilds "2.0.4"

    "puts OpenID::SRed" yeilds "nil"

    yet when i "open /opt/local/lib/ruby/gems/1.8/gems/ruby-openid-2.0.4/lib/openid/extensions"

    i clearly see sreg.rb in the folder

    a few people have mentioned the need to patch the plugin using "http://dev.rubyonrails.org/ticket/10604" though I think this is only for openid authentication plugin's that are pre 2.0, and i have no idea how to implement the patch if i did indeed need it.

    If anyone else had the same problem and fixed it, or if you need some more info about my setup before you can help, then please let me know!!

  • Avatar
    25 July 2008

    Benjamin,

    Why do you ever need to expire the session if you are just going to log the user in automatically when they come back?

Add a comment