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