| 1 |
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. It also relies on a number of |
|---|
| 18 |
database tables to store the authentication keys. So you'll have to run the migration to create these before you get started: |
|---|
| 19 |
|
|---|
| 20 |
rake open_id_authentication:db:create |
|---|
| 21 |
|
|---|
| 22 |
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: |
|---|
| 23 |
|
|---|
| 24 |
OpenIdAuthentication.store = :file |
|---|
| 25 |
|
|---|
| 26 |
This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations. |
|---|
| 27 |
If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb. |
|---|
| 28 |
|
|---|
| 29 |
This plugin relies on Rails Edge revision 6317 or newer. |
|---|
| 30 |
|
|---|
| 31 |
|
|---|
| 32 |
Example |
|---|
| 33 |
======= |
|---|
| 34 |
|
|---|
| 35 |
This example is just to meant to demonstrate how you could use OpenID authentication. You'll might well want to add |
|---|
| 36 |
salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point, |
|---|
| 37 |
not a destination. |
|---|
| 38 |
|
|---|
| 39 |
config/routes.rb |
|---|
| 40 |
|
|---|
| 41 |
map.open_id_complete 'session', :controller => "sessions", :action => "create", :requirements => { :method => :get } |
|---|
| 42 |
map.resource :session |
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
app/views/sessions/new.erb |
|---|
| 46 |
|
|---|
| 47 |
<% form_tag(session_url) do %> |
|---|
| 48 |
<p> |
|---|
| 49 |
<label for="name">Username:</label> |
|---|
| 50 |
<%= text_field_tag "name" %> |
|---|
| 51 |
</p> |
|---|
| 52 |
|
|---|
| 53 |
<p> |
|---|
| 54 |
<label for="password">Password:</label> |
|---|
| 55 |
<%= password_field_tag %> |
|---|
| 56 |
</p> |
|---|
| 57 |
|
|---|
| 58 |
<p> |
|---|
| 59 |
...or use: |
|---|
| 60 |
</p> |
|---|
| 61 |
|
|---|
| 62 |
<p> |
|---|
| 63 |
<label for="openid_url">OpenID:</label> |
|---|
| 64 |
<%= text_field_tag "openid_url" %> |
|---|
| 65 |
</p> |
|---|
| 66 |
|
|---|
| 67 |
<p> |
|---|
| 68 |
<%= submit_tag 'Sign in', :disable_with => "Signing in…" %> |
|---|
| 69 |
</p> |
|---|
| 70 |
<% end %> |
|---|
| 71 |
|
|---|
| 72 |
app/controllers/sessions_controller.rb |
|---|
| 73 |
class SessionsController < ApplicationController |
|---|
| 74 |
def create |
|---|
| 75 |
if using_open_id? |
|---|
| 76 |
open_id_authentication |
|---|
| 77 |
else |
|---|
| 78 |
password_authentication(params[:name], params[:password]) |
|---|
| 79 |
end |
|---|
| 80 |
end |
|---|
| 81 |
|
|---|
| 82 |
|
|---|
| 83 |
protected |
|---|
| 84 |
def password_authentication(name, password) |
|---|
| 85 |
if @current_user = @account.users.authenticate(params[:name], params[:password]) |
|---|
| 86 |
successful_login |
|---|
| 87 |
else |
|---|
| 88 |
failed_login "Sorry, that username/password doesn't work" |
|---|
| 89 |
end |
|---|
| 90 |
end |
|---|
| 91 |
|
|---|
| 92 |
def open_id_authentication |
|---|
| 93 |
authenticate_with_open_id do |result, identity_url| |
|---|
| 94 |
if result.successful? |
|---|
| 95 |
if @current_user = @account.users.find_by_identity_url(identity_url) |
|---|
| 96 |
successful_login |
|---|
| 97 |
else |
|---|
| 98 |
failed_login "Sorry, no user by that identity URL exists (#{identity_url})" |
|---|
| 99 |
end |
|---|
| 100 |
else |
|---|
| 101 |
failed_login result.message |
|---|
| 102 |
end |
|---|
| 103 |
end |
|---|
| 104 |
end |
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 |
private |
|---|
| 108 |
def successful_login |
|---|
| 109 |
session[:user_id] = @current_user.id |
|---|
| 110 |
redirect_to(root_url) |
|---|
| 111 |
end |
|---|
| 112 |
|
|---|
| 113 |
def failed_login(message) |
|---|
| 114 |
flash[:error] = message |
|---|
| 115 |
redirect_to(new_session_url) |
|---|
| 116 |
end |
|---|
| 117 |
end |
|---|
| 118 |
|
|---|
| 119 |
|
|---|
| 120 |
|
|---|
| 121 |
If you're fine with the result messages above and don't need individual logic on a per-failure basis, |
|---|
| 122 |
you can collapse the case into a mere boolean: |
|---|
| 123 |
|
|---|
| 124 |
def open_id_authentication |
|---|
| 125 |
authenticate_with_open_id do |result, identity_url| |
|---|
| 126 |
if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url) |
|---|
| 127 |
successful_login |
|---|
| 128 |
else |
|---|
| 129 |
failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})") |
|---|
| 130 |
end |
|---|
| 131 |
end |
|---|
| 132 |
end |
|---|
| 133 |
|
|---|
| 134 |
|
|---|
| 135 |
Simple Registration OpenID Extension |
|---|
| 136 |
==================================== |
|---|
| 137 |
|
|---|
| 138 |
Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension |
|---|
| 139 |
|
|---|
| 140 |
You can support it in your app by changing #open_id_authentication |
|---|
| 141 |
|
|---|
| 142 |
def open_id_authentication(identity_url) |
|---|
| 143 |
# Pass optional :required and :optional keys to specify what sreg fields you want. |
|---|
| 144 |
# Be sure to yield registration, a third argument in the #authenticate_with_open_id block. |
|---|
| 145 |
authenticate_with_open_id(identity_url, |
|---|
| 146 |
:required => [ :nickname, :email ], |
|---|
| 147 |
:optional => :fullname) do |status, identity_url, registration| |
|---|
| 148 |
case status |
|---|
| 149 |
when :missing |
|---|
| 150 |
failed_login "Sorry, the OpenID server couldn't be found" |
|---|
| 151 |
when :canceled |
|---|
| 152 |
failed_login "OpenID verification was canceled" |
|---|
| 153 |
when :failed |
|---|
| 154 |
failed_login "Sorry, the OpenID verification failed" |
|---|
| 155 |
when :successful |
|---|
| 156 |
if @current_user = @account.users.find_by_identity_url(identity_url) |
|---|
| 157 |
assign_registration_attributes!(registration) |
|---|
| 158 |
|
|---|
| 159 |
if current_user.save |
|---|
| 160 |
successful_login |
|---|
| 161 |
else |
|---|
| 162 |
failed_login "Your OpenID profile registration failed: " + |
|---|
| 163 |
@current_user.errors.full_messages.to_sentence |
|---|
| 164 |
end |
|---|
| 165 |
else |
|---|
| 166 |
failed_login "Sorry, no user by that identity URL exists" |
|---|
| 167 |
end |
|---|
| 168 |
end |
|---|
| 169 |
end |
|---|
| 170 |
end |
|---|
| 171 |
|
|---|
| 172 |
# registration is a hash containing the valid sreg keys given above |
|---|
| 173 |
# use this to map them to fields of your user model |
|---|
| 174 |
def assign_registration_attributes!(registration) |
|---|
| 175 |
model_to_registration_mapping.each do |model_attribute, registration_attribute| |
|---|
| 176 |
unless registration[registration_attribute].blank? |
|---|
| 177 |
@current_user.send("#{model_attribute}=", registration[registration_attribute]) |
|---|
| 178 |
end |
|---|
| 179 |
end |
|---|
| 180 |
end |
|---|
| 181 |
|
|---|
| 182 |
def model_to_registration_mapping |
|---|
| 183 |
{ :login => 'nickname', :email => 'email', :display_name => 'fullname' } |
|---|
| 184 |
end |
|---|
| 185 |
|
|---|
| 186 |
|
|---|
| 187 |
Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license |
|---|