Rails, OpenID, and Acts as Authenticated
5 Mar 2007This weekend I added OpenID to a Rails application for the first time, and this blog post describes the steps I took to integrate OpenID with Acts as Authenticated for account creation and access.
First I installed David’s OpenID Rails plugin (as discussed at David’s blog) into my application which was already using AAA to handle account creations and logins. I then created the following migration to add the OpenID identity URL to my user model:
def self.up
add_column :users, :identity_url, :string
end
def self.down
remove_column :users, :identity_url
end
end
And I changed the User model to allow accounts to be created either with login/email/password or with only an identity url (only changed lines are listed):
validates_presence_of :login,
:email, :if => :not_openid?
validates_length_of :login,
:within => 3..40, :if => :not_openid?
validates_length_of :email,
:within => 3..100, :if => :not_openid?
validates_uniqueness_of :login, :email, :salt, :allow_nil => true
def password_required?
not_openid? && (crypted_password.blank? or not password.blank?)
end
def not_openid?
identity_url.blank?
end
end
This allows me to create User records without the usual required fields as long as the user created the account via an OpenID login.
And finally, the controller changes:
def login
if using_open_id?
open_id_authentication
elsif params[:login]
password_authentication(params[:login], params[:password])
end
end
protected
def password_authentication(login, password)
if self.current_user = User.authenticate(params[:login], params[:password])
successful_login
else
failed_login("Invalid login or password")
end
end
def open_id_authentication
authenticate_with_open_id do |result, identity_url|
if result.successful?
if self.current_user = User.find_or_create_by_identity_url(identity_url)
successful_login
else
failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
end
else
failed_login result.message
end
end
end
private
def successful_login
redirect_back_or_default(index_url)
flash[:notice] = "Logged in successfully"
end
def failed_login(message)
redirect_to(:action => ‘login’)
flash[:warning] = message
end
end
That’s it! You can see it in action at the Rails plugin directory.
Update
I updated this code to match the plugin changes that were made between the time I installed the plugin and the time I posted this entry. :)
Update 2
I made another change to the code based on Geoff’s comment. Thanks, Geoff!







Nice post. We’ve been looking into the OpenID thing and it definitely looks like it’s a simple enough thing to add.
[...] This morning, Ben Curtis put up an excellent, short tutorial on using this plugin on an existing site. [...]
Ben,
With the latest rev of the DHH plugin (6334), did you hit this error when processing openid complete?
NoMethodError (You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.downcase):
.//vendor/plugins/open_id_authentication/lib/open_id_authentication.rb:43:in `normalize_url’
.//vendor/plugins/open_id_authentication/lib/open_id_authentication.rb:62:in `normalize_url’
.//vendor/plugins/open_id_authentication/lib/open_id_authentication.rb:95:in `complete_open_id_authentication’
I may have messed something up in my config…
Bernie
Yes, Bernie, I ran into a problem with this changeset which changed how the results were returned in complete_open_id_authentication. In my copy of the plugin I changed those yields back to just returning the symbols to match what my controller code was expecting.
Thanks, Ben! I reverted the same. Took me a while to figure out what was going on, made worse missing else clause in the README recommended code
def open_id_authentication
authenticate_with_open_id do |result, identity_url|
case result
when :missing
....
Which caused the mismatch result codes to fall through. Thanks again.
I just added “attr_reader :code” to the beginning of OpenIdAuthentication::Result and used result.code in the case statement.
[...] 3) Ben Curtis demonstrates how to tie in OpenID with the popular Acts As Authenticated plugin. [...]
In the AccountController, where is using_open_id? defined? I’ve tried to follow along with your example but Rails dies with unknown method.
FWIW, I’ve been trying for weeks to combine OpenID authentication with a straightforward role-based authorization scheme (e.g. the Authorized plugin.) The OpenID guestbook at http://rorek.org/blog/Simple_Rails_OpenID_Guestbook got very close as did http://identity.eastmedia.com/identity/show/Bookmarks+Demo+Application but neither consider roles. Backfitting even simple RBAC into the demo apps has been prohibitively painful.
@Bob:
It’s defined in the plugin.
does anyone here know how one would do the registration for this?
DHH in his example with registration, checks for the identity_url in the user table, and if found, adds the user info.
So the users have to register their url before? I don’t really understand this.
Wouldn’t it be better to just add the identity if it is not existing, and update the details, on login if it is found?
Instead of messing with the plugin code you can boil that whole case statement down if you use the OpenIdAuthentication::Result#successful? or OpenIdAuthentication::Result#unsuccessful? methods. Here is how I my open_id_authentication method looks.
I don’t know if this will layout right in the comment. Ben feel free to fix it for me ;)
authenticate_with_open_id do |result, identity_url|
if result.successful?
if self.current_user = User.find_or_create_by_identity_url(identity_url)
successful_login
else
failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
end
else
failed_login result.message
end
end
end
Thanks for the guide. Helped me integrate it with the RestfulAuthentication plugin.
@Geoff
Thanks for the tip. I have updated the code in the post.
[...] Si trabajas en Rails existen muchos recursos e incluso una gema para lograrlo y también de como ajustarlo al famoso acts_as_authenticated. [...]
Ah – I missed all the hidden steps of installing the open_id_authentication plugin, running migrations, setting index_url, etc. It’s still not clear in the code how one signs up with an OpenID URL – it takes more than simply adding an openid_url field in signup.rhtml
Thanks for clarifying this a bit; it’s just demoralizing trying to tie together OpenID, one of a half-dozen authentication plugins/engines/schemes, and a straightforward authorization system. As usual, what ought to be a simple matter of installing a turnkey auth scheme is taking far more effort than the entire rest of the application. This has been true each of the three times I’ve attempted a Rails application and I don’t see things getting better overall…
If I can get some cooperation from the Austin on Rails group, I’ll solve this problem for the next 6 months (until core Rails changes enough to deprecate key components without providing replacements – I’m thinking of UserEngine…) and write it up so I’m not accused of pointless bitching. :)