Dear RA,
We’ve had some good times. You were my first and only authentication plugin. I’ll always remember you, but it’s time to move on. There’s this hot, young plugin on the block named AL that I’ve fallen for. So long. *tear*
Rick Olsen’s restful_authentication is out and Ben Johnson’s authlogic is in as the hot Ruby (and/or Rails) authentication module these days. No disrespect to Rick and restful_authentication. It’s got sound security principles behind it, but dealing with the implementation can be a headache. If you’re unfamiliar with restful_authentication, the first thing you would do after installing the plugin is run some generator scripts. These scripts create a bunch of code in your Rails project. Where that becomes a problem is if you have to modify the code and then later want to run the generator again.
Authlogic takes the stance that you should never have to run a generator. It also has a lot of auto-configuration (based on the fields found in your user model). Sessions in authlogic make use of the familiar, standard RESTful controller. The gem itself is also very well organized with things being broken down into modules. The only downside I’ve comes across is I can’t find a page to show me all of the configuration options at once; hardly something to pick on.
Instead of trying to write the be-all end-all guide, this will be more of a case study. The nature of using a configurable generator, as restful_authentication provides, is that it would be tough to cover all cases. In addition, my app had some closely related components, such as password resetting, that needed to also be changed.
The first thing I did was install authlogic as a gem and pull it into my application. If you’ve spent any time with Rails 2.2 or higher, then this should be old hat for you by now.
sudo gem install authlogic
Then add the following to your config/environment.rb file:
In your user model, add the following:
acts_as_authentic do |c|
c.transition_from_restful_authentication = true
end
With authlogic in place, you can remove restful_authentication. The plugin directory can be removed from vendor/plugins. You will also need to remove the lib/authenticated_system.rb and lib/authenticated_test_helper.rb files as well as the includes for them. These should be in application_controller.rb and test_helper.rb respectively if you followed restful_authentication’s instructions.
From here, a number of things still need to happen and it doesn’t much matter the order. There need to be some database changes to the users table. A controller for sessions needs to be added or an existing one needs to be modified. The test suite will be severely broken with a large number of tests failing or generating errors. There is also a lot of code to clean up in the user model and controller. As mentioned earlier, there may also be some related pieces which need clean up or conversion such as password resets.
What I ended up doing was half-following Ben Johnson’s guide for setting up Authlogic and adapting it to the existing code. In this process I stripped out all of the generated code from the user model and controller, generated a transition migration, and copied a few views for the users_controller.rb from the sample application.
The sessions_controller from restful_authentication could be modified, however, I found it easier to start from scratch. I copied the controller and views from the authlogic setup guide and made some small changes to the views to adapt them to my layout. You will also need the following model at minimum:
class UserSession < Authlogic::Session::Base
end
Because you inherit from Authlogic::Session::Base, there is no actual code in the session model. Some people like to work with the session variables through a model, you can add this code if you’d like but the default should get you all you need to begin.
A popular feature to add to any authentication system is a method to request a forgotten password. Ben also provides a guide for how to handle this with authlogic. As with the sessions, password resets were a concise and contained component. It was easier to wipe out what was existing and start fresh with the aforementioned guide.
Authlogic follows Rails’ convention over configuration idea and will pick up functionality based on the fields that exist in your model. Of course this means changes to the database and in Rails, that means a migration. Below is what mine looks like:
class TransitionToAuthlogic < ActiveRecord::Migration
def self.up
change_column :users, :crypted_password, :string, :limit => 128, :null => false, :default => ""
change_column :users, :salt, :string, :limit => 128, :null => false, :default => ""
rename_column :users, :salt, :password_salt
rename_column :users, :activation_code, :old_activation_code
rename_column :users, :password_reset_code, :old_password_reset_code
rename_column :users, :remember_token, :old_remember_token
rename_column :users, :remember_token_expires_at, :old_remember_token_expires_at
add_column :users, :login_count, :integer, :null => false, :default => 0
add_column :users, :failed_login_count, :integer, :null => false, :default => 0
add_column :users, :last_request_at, :datetime
add_column :users, :current_login_at, :datetime
add_column :users, :last_login_at, :datetime
add_column :users, :current_login_ip, :string
add_column :users, :last_login_ip, :string
add_column :users, :persistence_token, :string, :null => false
add_column :users, :single_access_token, :string, :null => false
add_column :users, :perishable_token, :string, :null => false
add_column :users, :active, :boolean, :default => false, :null => false
# set active users to active.
User.update_all("active = 1", "state = 'active'")
add_index :users, :perishable_token
end
def self.down
remove_column :users, :active
remove_column :users, :perishable_token
remove_column :users, :single_access_token
remove_column :users, :persistence_token
remove_column :users, :last_login_ip
remove_column :users, :current_login_ip
remove_column :users, :last_login_at
remove_column :users, :current_login_at
remove_column :users, :last_request_at
remove_column :users, :failed_login_count
remove_column :users, :login_count
rename_column :users, :password_salt, :salt
rename_column :users, :old_activation_code, :activation_code
rename_column :users, :old_password_reset_code, :password_reset_code
rename_column :users, :old_remember_token, :remember_token
rename_column :users, :old_remember_token_expires_at, :remember_token_expires_at
end
end
Most important are the two change_column calls at the top. Authlogic needs 128 characters to store the crypted_password and salt fields whereas restful_authentication only provides 40. It doesn't really matter if they are still 128 characters when the migration is run down so I didn't bother changing them back. Besides, you'd never do that in production anyway.
As mentioned previously, there is an active column being added to the user model. Restful_authentication has an option to enable user states. This is done to allow tracking of activation. Authlogic doesn't come with anything such as this built in. It's simple enough to add and someone has written a tutorial already. The migration handles carrying the active status over from restful_authentication with a single update_all call.
Notice the rename_columns; the idea is that those columns are essentially flagged for later removal. This is a somewhat cautious approach. It would be possible to handle the removal of these columns in a later transaction.
The final piece to the puzzle is making your test suite happy again. If you are at all serious about testing, you will now have a number of failing tests after making such widespread changes to your application. In my application, much of the functional testing involved logging in user with a specific role. To do this in authlogic is simple.
First, add support for authlogic in tests by requiring the authlogic test module in test_helper.rb.
require 'authlogic/test_case'
Then, add the following to any functional or integration test you need to log in to test:
setup :activate_authlogic
Finally, you can initiate the actual login by calling UserSession.create and passing in your user object like so:
assert UserSession.create(users(:admin))
Notice we can even assert that the result of this login is true so we know it actually happens. The example happens to pass in a user from a fixture, but you could just as easily use a factory to generate your user object. Authlogic doesn't care where the user comes from.
Assuming all of your tests pass and you've done enough manual testing to be satisfied, you can go ahead and declare your switch from restful_authentication to authlogic complete. Changing over to a new authentication module can be a scary thing, especially when you are changing encryption methods for your passwords. It has the potential to lock out every single user in your system if you get something wrong. Remember, TATFT. A solid test suite and thorough manual testing will give you confidence when it comes to launching this into production with your application. If you do your due diligence, your transition will be smooth.
I created this post based on the work I did for DecoyMusic.com. It is now happily chugging along on Authlogic. Now that the transition is done, it's time to start using authlogic's plugin architecture to add OpenID and Facebook Connect support.