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