To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / .svn / pristine / bc / bcf601e4c2eab267682f5366299899e20f6d08d2.svn-base @ 1297:0a574315af3e
History | View | Annotate | Download (7.95 KB)
| 1 | 1296:038ba2d95de8 | Chris | OpenIdAuthentication |
|---|---|---|---|
| 2 | ==================== |
||
| 3 | |||
| 4 | Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first: |
||
| 5 | |||
| 6 | gem install ruby-openid |
||
| 7 | |||
| 8 | To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb |
||
| 9 | from that gem. |
||
| 10 | |||
| 11 | The specification used is http://openid.net/specs/openid-authentication-2_0.html. |
||
| 12 | |||
| 13 | |||
| 14 | Prerequisites |
||
| 15 | ============= |
||
| 16 | |||
| 17 | OpenID authentication uses the session, so be sure that you haven't turned that off. |
||
| 18 | |||
| 19 | 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: |
||
| 20 | |||
| 21 | OpenIdAuthentication.store = :file |
||
| 22 | |||
| 23 | This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations. |
||
| 24 | If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb. |
||
| 25 | |||
| 26 | 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: |
||
| 27 | |||
| 28 | map.root :controller => 'articles' |
||
| 29 | |||
| 30 | This plugin relies on Rails Edge revision 6317 or newer. |
||
| 31 | |||
| 32 | |||
| 33 | Example |
||
| 34 | ======= |
||
| 35 | |||
| 36 | This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add |
||
| 37 | salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point, |
||
| 38 | not a destination. |
||
| 39 | |||
| 40 | 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 |
||
| 41 | model you are using for authentication. |
||
| 42 | |||
| 43 | Also of note is the following code block used in the example below: |
||
| 44 | |||
| 45 | authenticate_with_open_id do |result, identity_url| |
||
| 46 | ... |
||
| 47 | end |
||
| 48 | |||
| 49 | 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' - |
||
| 50 | If you are storing just 'example.com' with your user, the lookup will fail. |
||
| 51 | |||
| 52 | There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs. |
||
| 53 | |||
| 54 | OpenIdAuthentication.normalize_url(user.identity_url) |
||
| 55 | |||
| 56 | The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/' |
||
| 57 | It will also raise an InvalidOpenId exception if the URL is determined to not be valid. |
||
| 58 | Use the above code in your User model and validate OpenID URLs before saving them. |
||
| 59 | |||
| 60 | config/routes.rb |
||
| 61 | |||
| 62 | map.root :controller => 'articles' |
||
| 63 | map.resource :session |
||
| 64 | |||
| 65 | |||
| 66 | app/views/sessions/new.erb |
||
| 67 | |||
| 68 | <% form_tag(session_url) do %> |
||
| 69 | <p> |
||
| 70 | <label for="name">Username:</label> |
||
| 71 | <%= text_field_tag "name" %> |
||
| 72 | </p> |
||
| 73 | |||
| 74 | <p> |
||
| 75 | <label for="password">Password:</label> |
||
| 76 | <%= password_field_tag %> |
||
| 77 | </p> |
||
| 78 | |||
| 79 | <p> |
||
| 80 | ...or use: |
||
| 81 | </p> |
||
| 82 | |||
| 83 | <p> |
||
| 84 | <label for="openid_identifier">OpenID:</label> |
||
| 85 | <%= text_field_tag "openid_identifier" %> |
||
| 86 | </p> |
||
| 87 | |||
| 88 | <p> |
||
| 89 | <%= submit_tag 'Sign in', :disable_with => "Signing in…" %> |
||
| 90 | </p> |
||
| 91 | <% end %> |
||
| 92 | |||
| 93 | app/controllers/sessions_controller.rb |
||
| 94 | class SessionsController < ApplicationController |
||
| 95 | def create |
||
| 96 | if using_open_id? |
||
| 97 | open_id_authentication |
||
| 98 | else |
||
| 99 | password_authentication(params[:name], params[:password]) |
||
| 100 | end |
||
| 101 | end |
||
| 102 | |||
| 103 | |||
| 104 | protected |
||
| 105 | def password_authentication(name, password) |
||
| 106 | if @current_user = @account.users.authenticate(params[:name], params[:password]) |
||
| 107 | successful_login |
||
| 108 | else |
||
| 109 | failed_login "Sorry, that username/password doesn't work" |
||
| 110 | end |
||
| 111 | end |
||
| 112 | |||
| 113 | def open_id_authentication |
||
| 114 | authenticate_with_open_id do |result, identity_url| |
||
| 115 | if result.successful? |
||
| 116 | if @current_user = @account.users.find_by_identity_url(identity_url) |
||
| 117 | successful_login |
||
| 118 | else |
||
| 119 | failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
|
||
| 120 | end |
||
| 121 | else |
||
| 122 | failed_login result.message |
||
| 123 | end |
||
| 124 | end |
||
| 125 | end |
||
| 126 | |||
| 127 | |||
| 128 | private |
||
| 129 | def successful_login |
||
| 130 | session[:user_id] = @current_user.id |
||
| 131 | redirect_to(root_url) |
||
| 132 | end |
||
| 133 | |||
| 134 | def failed_login(message) |
||
| 135 | flash[:error] = message |
||
| 136 | redirect_to(new_session_url) |
||
| 137 | end |
||
| 138 | end |
||
| 139 | |||
| 140 | |||
| 141 | |||
| 142 | If you're fine with the result messages above and don't need individual logic on a per-failure basis, |
||
| 143 | you can collapse the case into a mere boolean: |
||
| 144 | |||
| 145 | def open_id_authentication |
||
| 146 | authenticate_with_open_id do |result, identity_url| |
||
| 147 | if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url) |
||
| 148 | successful_login |
||
| 149 | else |
||
| 150 | failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})")
|
||
| 151 | end |
||
| 152 | end |
||
| 153 | end |
||
| 154 | |||
| 155 | |||
| 156 | Simple Registration OpenID Extension |
||
| 157 | ==================================== |
||
| 158 | |||
| 159 | Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension |
||
| 160 | |||
| 161 | You can support it in your app by changing #open_id_authentication |
||
| 162 | |||
| 163 | def open_id_authentication(identity_url) |
||
| 164 | # Pass optional :required and :optional keys to specify what sreg fields you want. |
||
| 165 | # Be sure to yield registration, a third argument in the #authenticate_with_open_id block. |
||
| 166 | authenticate_with_open_id(identity_url, |
||
| 167 | :required => [ :nickname, :email ], |
||
| 168 | :optional => :fullname) do |result, identity_url, registration| |
||
| 169 | case result.status |
||
| 170 | when :missing |
||
| 171 | failed_login "Sorry, the OpenID server couldn't be found" |
||
| 172 | when :invalid |
||
| 173 | failed_login "Sorry, but this does not appear to be a valid OpenID" |
||
| 174 | when :canceled |
||
| 175 | failed_login "OpenID verification was canceled" |
||
| 176 | when :failed |
||
| 177 | failed_login "Sorry, the OpenID verification failed" |
||
| 178 | when :successful |
||
| 179 | if @current_user = @account.users.find_by_identity_url(identity_url) |
||
| 180 | assign_registration_attributes!(registration) |
||
| 181 | |||
| 182 | if current_user.save |
||
| 183 | successful_login |
||
| 184 | else |
||
| 185 | failed_login "Your OpenID profile registration failed: " + |
||
| 186 | @current_user.errors.full_messages.to_sentence |
||
| 187 | end |
||
| 188 | else |
||
| 189 | failed_login "Sorry, no user by that identity URL exists" |
||
| 190 | end |
||
| 191 | end |
||
| 192 | end |
||
| 193 | end |
||
| 194 | |||
| 195 | # registration is a hash containing the valid sreg keys given above |
||
| 196 | # use this to map them to fields of your user model |
||
| 197 | def assign_registration_attributes!(registration) |
||
| 198 | model_to_registration_mapping.each do |model_attribute, registration_attribute| |
||
| 199 | unless registration[registration_attribute].blank? |
||
| 200 | @current_user.send("#{model_attribute}=", registration[registration_attribute])
|
||
| 201 | end |
||
| 202 | end |
||
| 203 | end |
||
| 204 | |||
| 205 | def model_to_registration_mapping |
||
| 206 | { :login => 'nickname', :email => 'email', :display_name => 'fullname' }
|
||
| 207 | end |
||
| 208 | |||
| 209 | Attribute Exchange OpenID Extension |
||
| 210 | =================================== |
||
| 211 | |||
| 212 | 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 |
||
| 213 | |||
| 214 | 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: |
||
| 215 | |||
| 216 | authenticate_with_open_id(identity_url, |
||
| 217 | :required => [ :email, 'http://schema.openid.net/birthDate' ]) do |result, identity_url, registration| |
||
| 218 | |||
| 219 | This would provide the sreg data for :email, and the AX data for 'http://schema.openid.net/birthDate' |
||
| 220 | |||
| 221 | |||
| 222 | |||
| 223 | Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license |