Authlogic and OpenID on RailsMay 11 2009
So, I’m working on a Rails App and I want to use OpenID (and only OpenID) for authentication. I was going to use Restful_Authentication with the open_id_authentication extension, but then I saw Ryan Bates’ Railscast on AuthLogic. Authlogic has an OpenID extention which looked perfect for my needs, and Authlogic seemed like a great gem for authentication. My goal was simple: I wanted to support, and only support, authentication via OpenID. None of this username/password/salt stuff.
Now, I should warn you: I HAVE NO IDEA WHAT I AM DOING. I’m just getting started with Rails so I hit a few bumps getting this working. I learned a lot along the way, so here’s a quick rundown of my adventure with Authlogic and OpenID on Rails.
First, getting started with sample code is always a good idea. Authlogic has an example app on github where you can check out how to use the gem. After pulling down the code, I was surprised I couldn’t find anything relating to OpenID. It turns out, the master repository doesn’t have the OpenID example. But there’s a branch of the master example that does have the OpenID functionality. Here’s how to get it:
First, clone the Authlogic example like so:
git clone git://github.com/binarylogic/authlogic_example.git
Then, cd into the new Authlogic directory and get the OpenID fork like so:
git checkout --track -b authlogic_with_openid origin/with-openid
Then, you can explore away. My approach was simple: using the OpenID example as a guide, I’d start a new app and add OpenID support from Authlogic.
First, I need the required gems. Authlogic and authlogic-oid are obvious, but I also need ruby-openid. Authlogic-oid is built on the rails plugin open_id_authentication, which in turn is built on ruby-openid.
sudo gem install ruby-openid sudo gem install authlogic sudo gem install authlogic-oid
You can also set these in your environment.rb file, like so:
config.gem "authlogic" config.gem "authlogic-oid", :lib => "authlogic_openid" config.gem "ruby-openid", :lib => "openid"
Note: I got thwarted when I tried running the app using the config.gem approach. I kept getting an “Uninitialized constant Authlogic” in User_sessions.rb when I ran the app. It sucked! It took me a while to figure this out, but it was a horrible beginner mistake. I was doing rake gems:install instead of sudo rake gems:install. So when I ran my app, the proper gems weren’t in the right place. Ouch!
Next, it’s time to install the open_id_authentication plugin which is required by Authlogic-oid. It’s not available as a gem.
script/plugin install git://github.com/rails/open_id_authentication.git
The next step is to create the necessary migration scripts for the open_id_authentication plugin.
Another beginner mistake: I got a failure when I tried doing open_id_authentication:db:create before I installed the Authlogic gem. Something about “acts_as_authenticated” wasn’t there. So the order of installation is important!
Next, use the authlogic command to create the user_session model. I’m prettying much following instructions from the Authlogic github pages at this point:
script/generate session user_session script/generate model user
Another beginner mistake: if you’re making changes to your migration files without creating a new migration, make sure your schema is correct. I don’t know the best way to do this except by using rake db:drop, rake db:create, and rake db:migrate. This was due to an error I was seeing in my view: “undefined method :openid_identifier”. I had a text_field in my form for the openid field, and I had the openid_identifier field in my model. The problem? It wasn’t in the database schema so Rails couldn’t do its thing to make it a property of the user model and render the textfield correctly.
From there, the views and controllers are pretty much the same as the example. My user model is also pretty slim:
class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :email, :null=>false t.string :persistence_token, :null=> false t.string :openid_identifier, :null=> false t.datetime :last_request_at t.timestamps end add_index :users, :openid_identifier add_index :users, :persistence_token end def self.down drop_table :users end end
Now, when I put everything together to run the app, I got some weird failures. When trying to login using my OpenID, I got a nasty error:
undefined local variable or method 'crypted_password_field'
Not having a crypted_pasword field made sense seeing how I wasn’t supporting login or password fields. But I wasn’t expecting not having it to be an issue. In fact, from the docs the only required field on the user model is :persistance_token. So what’s going on?
Well, it turns out the Authlogic OpenID extension is designed to work with a login/password by default. I had to dig through the stack trace and source code, but was able to figure out what’s going on. There’s a method called attributes_to_save which is responsible for persisting form fields across the OpenID process. By default, attributes_to_save includes password related information. It treats the crypted password and password salt fields a little differently, which causes a problem when you don’t have a :crypted_password attribute on your model. The solution is simple: just override the method with one which doesn’t include the password fields. The user model will look like this:
class User < ActiveRecord::Base acts_as_authentic do |c| def attributes_to_save # :doc: attrs_to_save = attributes.clone.delete_if do |k, v| [ :persistence_token, :perishable_token, :single_access_token, :login_count, :failed_login_count, :last_request_at, :current_login_at, :last_login_at, :current_login_ip, :last_login_ip, :created_at, :updated_at, :lock_version].include?(k.to_sym) end end end end
The second mistake was because I got ahead of myself. In the example app there’s code in the application.html.erb file rendering a login/register link or user info based on the current_user method in the application controller. I was getting an error:
unknown method 'logged_out?'
which was occurring deep in the Authlogic codebase. The problem was I didn’t go a good job copying everything I needed from the example files! The authlogic example project used the
filter on the UserSession method. After digging through the documentation, this callback relies on the
<pre class="brush: ruby; title: ; notranslate" title="">
<p> </span> </p> <p> <span class="n">field on the user model, which I didn’t have at the time. And not having this field was throwing everything off. (The best part was the documentation clearly states you need that attribute on the model for the callback to work correctly.<br /> </span> </p> <p> <span class="n">Lesson learned: always know what you’re doing. (Also: don’t be afraid of source code).<br /> </span> </p> <p> The whole process has really made me appreciate Authlogic. It’s very extensible and extremely easy to customize. If you know what you’re doing it’s pretty slick- the best way to figure it out is by reading the documentation and playing around with some code. </p> <p> Good luck! </p>