Chris@1115: OpenIdAuthentication Chris@1115: ==================== Chris@1115: Chris@1115: Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first: Chris@1115: Chris@1115: gem install ruby-openid Chris@1115: Chris@1115: To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb Chris@1115: from that gem. Chris@1115: Chris@1115: The specification used is http://openid.net/specs/openid-authentication-2_0.html. Chris@1115: Chris@1115: Chris@1115: Prerequisites Chris@1115: ============= Chris@1115: Chris@1115: OpenID authentication uses the session, so be sure that you haven't turned that off. Chris@1115: Chris@1115: Alternatively, you can use the file-based store, which just relies on on tmp/openids being present in RAILS_ROOT. But be aware that this store only works if you have a single application server. And it's not safe to use across NFS. It's recommended that you use the database store if at all possible. To use the file-based store, you'll also have to add this line to your config/environment.rb: Chris@1115: Chris@1115: OpenIdAuthentication.store = :file Chris@1115: Chris@1115: This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations. Chris@1115: If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb. Chris@1115: Chris@1115: The plugin also expects to find a root_url method that points to the home page of your site. You can accomplish this by using a root route in config/routes.rb: Chris@1115: Chris@1115: map.root :controller => 'articles' Chris@1115: Chris@1115: This plugin relies on Rails Edge revision 6317 or newer. Chris@1115: Chris@1115: Chris@1115: Example Chris@1115: ======= Chris@1115: Chris@1115: This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add Chris@1115: salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point, Chris@1115: not a destination. Chris@1115: Chris@1115: Note that the User model referenced in the simple example below has an 'identity_url' attribute. You will want to add the same or similar field to whatever Chris@1115: model you are using for authentication. Chris@1115: Chris@1115: Also of note is the following code block used in the example below: Chris@1115: Chris@1115: authenticate_with_open_id do |result, identity_url| Chris@1115: ... Chris@1115: end Chris@1115: Chris@1115: In the above code block, 'identity_url' will need to match user.identity_url exactly. 'identity_url' will be a string in the form of 'http://example.com' - Chris@1115: If you are storing just 'example.com' with your user, the lookup will fail. Chris@1115: Chris@1115: There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs. Chris@1115: Chris@1115: OpenIdAuthentication.normalize_url(user.identity_url) Chris@1115: Chris@1115: The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/' Chris@1115: It will also raise an InvalidOpenId exception if the URL is determined to not be valid. Chris@1115: Use the above code in your User model and validate OpenID URLs before saving them. Chris@1115: Chris@1115: config/routes.rb Chris@1115: Chris@1115: map.root :controller => 'articles' Chris@1115: map.resource :session Chris@1115: Chris@1115: Chris@1115: app/views/sessions/new.erb Chris@1115: Chris@1115: <% form_tag(session_url) do %> Chris@1115:

Chris@1115: Chris@1115: <%= text_field_tag "name" %> Chris@1115:

Chris@1115: Chris@1115:

Chris@1115: Chris@1115: <%= password_field_tag %> Chris@1115:

Chris@1115: Chris@1115:

Chris@1115: ...or use: Chris@1115:

Chris@1115: Chris@1115:

Chris@1115: Chris@1115: <%= text_field_tag "openid_identifier" %> Chris@1115:

Chris@1115: Chris@1115:

Chris@1115: <%= submit_tag 'Sign in', :disable_with => "Signing in…" %> Chris@1115:

Chris@1115: <% end %> Chris@1115: Chris@1115: app/controllers/sessions_controller.rb Chris@1115: class SessionsController < ApplicationController Chris@1115: def create Chris@1115: if using_open_id? Chris@1115: open_id_authentication Chris@1115: else Chris@1115: password_authentication(params[:name], params[:password]) Chris@1115: end Chris@1115: end Chris@1115: Chris@1115: Chris@1115: protected Chris@1115: def password_authentication(name, password) Chris@1115: if @current_user = @account.users.authenticate(params[:name], params[:password]) Chris@1115: successful_login Chris@1115: else Chris@1115: failed_login "Sorry, that username/password doesn't work" Chris@1115: end Chris@1115: end Chris@1115: Chris@1115: def open_id_authentication Chris@1115: authenticate_with_open_id do |result, identity_url| Chris@1115: if result.successful? Chris@1115: if @current_user = @account.users.find_by_identity_url(identity_url) Chris@1115: successful_login Chris@1115: else Chris@1115: failed_login "Sorry, no user by that identity URL exists (#{identity_url})" Chris@1115: end Chris@1115: else Chris@1115: failed_login result.message Chris@1115: end Chris@1115: end Chris@1115: end Chris@1115: Chris@1115: Chris@1115: private Chris@1115: def successful_login Chris@1115: session[:user_id] = @current_user.id Chris@1115: redirect_to(root_url) Chris@1115: end Chris@1115: Chris@1115: def failed_login(message) Chris@1115: flash[:error] = message Chris@1115: redirect_to(new_session_url) Chris@1115: end Chris@1115: end Chris@1115: Chris@1115: Chris@1115: Chris@1115: If you're fine with the result messages above and don't need individual logic on a per-failure basis, Chris@1115: you can collapse the case into a mere boolean: Chris@1115: Chris@1115: def open_id_authentication Chris@1115: authenticate_with_open_id do |result, identity_url| Chris@1115: if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url) Chris@1115: successful_login Chris@1115: else Chris@1115: failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})") Chris@1115: end Chris@1115: end Chris@1115: end Chris@1115: Chris@1115: Chris@1115: Simple Registration OpenID Extension Chris@1115: ==================================== Chris@1115: Chris@1115: Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension Chris@1115: Chris@1115: You can support it in your app by changing #open_id_authentication Chris@1115: Chris@1115: def open_id_authentication(identity_url) Chris@1115: # Pass optional :required and :optional keys to specify what sreg fields you want. Chris@1115: # Be sure to yield registration, a third argument in the #authenticate_with_open_id block. Chris@1115: authenticate_with_open_id(identity_url, Chris@1115: :required => [ :nickname, :email ], Chris@1115: :optional => :fullname) do |result, identity_url, registration| Chris@1115: case result.status Chris@1115: when :missing Chris@1115: failed_login "Sorry, the OpenID server couldn't be found" Chris@1115: when :invalid Chris@1115: failed_login "Sorry, but this does not appear to be a valid OpenID" Chris@1115: when :canceled Chris@1115: failed_login "OpenID verification was canceled" Chris@1115: when :failed Chris@1115: failed_login "Sorry, the OpenID verification failed" Chris@1115: when :successful Chris@1115: if @current_user = @account.users.find_by_identity_url(identity_url) Chris@1115: assign_registration_attributes!(registration) Chris@1115: Chris@1115: if current_user.save Chris@1115: successful_login Chris@1115: else Chris@1115: failed_login "Your OpenID profile registration failed: " + Chris@1115: @current_user.errors.full_messages.to_sentence Chris@1115: end Chris@1115: else Chris@1115: failed_login "Sorry, no user by that identity URL exists" Chris@1115: end Chris@1115: end Chris@1115: end Chris@1115: end Chris@1115: Chris@1115: # registration is a hash containing the valid sreg keys given above Chris@1115: # use this to map them to fields of your user model Chris@1115: def assign_registration_attributes!(registration) Chris@1115: model_to_registration_mapping.each do |model_attribute, registration_attribute| Chris@1115: unless registration[registration_attribute].blank? Chris@1115: @current_user.send("#{model_attribute}=", registration[registration_attribute]) Chris@1115: end Chris@1115: end Chris@1115: end Chris@1115: Chris@1115: def model_to_registration_mapping Chris@1115: { :login => 'nickname', :email => 'email', :display_name => 'fullname' } Chris@1115: end Chris@1115: Chris@1115: Attribute Exchange OpenID Extension Chris@1115: =================================== Chris@1115: Chris@1115: Some OpenID providers also support the OpenID AX (attribute exchange) protocol for exchanging identity information between endpoints. See more: http://openid.net/specs/openid-attribute-exchange-1_0.html Chris@1115: Chris@1115: Accessing AX data is very similar to the Simple Registration process, described above -- just add the URI identifier for the AX field to your :optional or :required parameters. For example: Chris@1115: Chris@1115: authenticate_with_open_id(identity_url, Chris@1115: :required => [ :email, 'http://schema.openid.net/birthDate' ]) do |result, identity_url, registration| Chris@1115: Chris@1115: This would provide the sreg data for :email, and the AX data for 'http://schema.openid.net/birthDate' Chris@1115: Chris@1115: Chris@1115: Chris@1115: Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license