Authlogic

Authlogic is a clean, simple, and unobtrusive ruby authentication solution. Put simply, its the Chuck Norris of authentication solutions for your framework of choice.

The last thing we need is another authentication solution, right? That’s what I thought until I tried out some of the current solutions in both rails and merb. None of them felt right. They were either too complicated, bloated, littered my application with tons of code, or were just confusing. This is not the simple / elegant ruby we all fell in love with. We need a “ruby like” authentication solution. Authlogic is my attempt to satisfy that need…

Let’s take a rails application…

Wouldn’t it be nice to keep your app up to date with the latest and greatest security techniques with a simple update of a plugin?

What if you could have authentication up and running in minutes without having to run a generator? All because it’s simple, like everything else.

What if creating a user session could be as simple as…

UserSession.create(params[:user_session])

What if your user sessions controller could look just like your other controllers…

class UserSessionsController < ApplicationController
  def new
    @user_session = UserSession.new
  end

  def create
    @user_session = UserSession.new(params[:user_session])
    if @user_session.save
      redirect_to 
    else
      render :action => :new
    end
  end

  def destroy
    current_user_session.destroy
  end
end

Look familiar? If you didn’t know any better, you would think UserSession was an ActiveRecord model. I think that’s pretty cool, because it fits nicely into the RESTful development pattern, a style we all know and love. What about the view…

<% form_for @user_session do |f| %>
  <%= f.error_messages %>
  <%= f.label :login %><br />
  <%= f.text_field :login %><br />
  <br />
  <%= f.label :password %><br />
  <%= f.password_field :password %><br />
  <br />
  <%= f.submit "Login" %>
<% end %>

Or how about persisting the session…

class ApplicationController
  helper_method :current_user_session, :current_user

  protected
    def current_user_session
      return @current_user_session if defined?(@current_user_session)
      @current_user_session = UserSession.find
    end

    end current_user
      return @current_user if defined?(@current_user)
      @current_user = current_user_session && current_user_session.user
    end
end

Authlogic makes this a reality. This is just the tip of the ice berg. Keep reading to find out everything Authlogic can do.

* Documentation: authlogic.rubyforge.org * Authlogic setup tutorial: www.binarylogic.com/2008/11/3/tutorial-authlogic-basic-setup * Live example of the setup tutorial above (with source): authlogic_example.binarylogic.com

Install and use

Install the gem / plugin (recommended)

$ sudo gem install authlogic

Now add the gem dependency in your config:

# config/environment.rb
config.gem "authlogic"

Or you install this as a plugin (for older versions of rails)

script/plugin install git://github.com/binarylogic/authlogic.git

Create your session

Lets assume you are setting up a session for your User model.

Create your user_session.rb file:

# app/models/user_session.rb
class UserSession < Authlogic::Session::Base
  # configuration here, just like ActiveRecord, or in an initializer
  # See Authlogic::Session::Config::ClassMethods for more details
end

Ensure proper database fields

The user model needs to have the following columns. The names of these columns can be changed with configuration. Better yet, Authlogic tries to guess these names by checking for the existence of common names. See Authlogic::Session::Config::ClassMethods for more details, but chances are you won’t have to specify any configuration for your field names, even if they aren’t the same names as below.

t.string    :login, :null => false
t.string    :crypted_password, :null => false
t.string    :password_salt, :null => false # not needed if you are encrypting your pw instead of using a hash algorithm
t.string    :remember_token, :null => false
t.integer   :login_count # This is optional, it is a "magic" column, just like "created_at". See below for a list of all magic columns.

Set up your model

Make sure you have a model that you will be authenticating with. For this example let’s say you have a User model:

class User < ActiveRecord::Base
  acts_as_authentic # for options see documentation: Authlogic::ORMAdapters::ActiveRecordAdapter::ActsAsAuthentic::Config
end

Done! Now go use it just like you would with any other ActiveRecord model. Either glance at the code at the beginning of this readme or check out the tutorial (see above in “helpful links”) for a more detailed walk through.

Magic Columns

Just like ActiveRecord has “magic” columns, such as: created_at and updated_at. Authlogic has its own “magic” columns too:

Column name           Description
login_count           Increased every time an explicit login is made. This will *NOT* increase if logging in by a session, cookie, or basic http auth
last_request_at       Updates every time the user logs in, either by explicitly logging in, or logging in by cookie, session, or http auth
current_login_at      Updates with the current time when an explicit login is made.
last_login_at         Updates with the value of current_login_at before it is reset.
current_login_ip      Updates with the request remote_ip when an explicit login is made.
last_login_ip         Updates with the value of current_login_ip before it is reset.

Magic States

Authlogic tries to check the state of the record before creating the session. If your record responds to the following methods and any of them return false, validation will fail:

Method name           Description
active?               Is the record marked as active?
approved?             Has the record been approved?
confirmed?            Has the record been conirmed?

What’s neat about this is that these are checked upon any type of login. When logging in explicitly, by cookie, session, or basic http auth. So if you mark a user inactive in the middle of their session they wont be logged back in next time they refresh the page. Giving you complete control.

Need Authlogic to check your own “state”? No problem, check out the hooks section below. Add in a before_validation to do your own checking. The sky is the limit.

Hooks / Callbacks

Just like ActiveRecord you can create your own hooks / callbacks so that you can do whatever you want when certain actions are performed. Here they are:

before_create
after_create
before_destroy
after_destroy
before_save
after_save
before_update
after_update
before_validation
after_validation

Multiple Sessions / Session Identifiers

You’re asking: “why would I want multiple sessions?”. Take this example:

You have an app where users login and then need to re-login to view / change their billing information. Similar to how Apple’s me.com works. What you could do is have the user login with their normal session, then have an entirely new session that represents their “secure” session. But wait, this is 2 users sessions. No problem:

# regular user session
@user_session = UserSession.new
@user_session.id
# => nil

# secure user session
@secure_user_session = UserSession.new(:secure)
@secure_user_session.id
# => :secure

This will keep everything separate. The :secure session will store its info in a separate cookie, separate session, etc. Just set the id and you are good to go. Need to retrieve the session?

@user_session = UserSession.find
@secure_user_session = UserSession.find(:secure)

For more information on ids checkout Authlogic::Session::Base#initialize

Single Access / Private Feeds Access

Need to provide a single / one time access to an account where the session does NOT get persisted? Take a private feed for example, if everyone followed standards basic http auth should work just fine, but since we live in a world where following standards is a hard concept (cough Microsoft cough), the feed url needs to have some sort of “credentials” to log the user in and get their user specific feed items. This is easy, Authlogic has a nifty little feature for doing just this. All that you need to do is add the following field in your table:

t.string :single_access_token, :null => false # or call it feeds_token or feed_token

Authlogic will notice you have this and adjust accordingly. You have the follow configuration options for your session (Authlogic::Session::Config) to customize how this works:

  1. params_key: params_key is the key Authlogic will look for when trying to find your session. It works just like your cookie and session key, except this is for params. Take a UserSession: www.mydomin.com?user_credentials=single_access_token

  2. single_access_allowed_request_types: Single access needs to be handled with care, after all, it gives the user access to their account. But maybe you don’t want to allow this for your entire application. Maybe you only want to allow this for certain request types, such as application/rss+xml or application/atom+xml. By default single access is only allowed for these requests types.

  3. single_access_token_field: This works just like remember_token_field. It basically allows you to name the column that the single_access_token is stored in.

  4. change_single_access_token_with_password

You also have the following options when calling acts_as_authentic (Authlogic::ORMAdapters::ActiveRecordAdapter::Config):

  1. single_access_token_field: Works the same as remember_token field.

  2. change_single_access_token_with_password: If the user changes their password do you want to change the single access token as well? This will require that they re-add the feed with the new token, as their old URL will not longer work. It’s really up to you if you want to do this. The other alternative is to provide an option when they are changing their password to change their “feed token” as well. You can call user.reset_single_access_token to do this yourself.

Please use this with care and make sure you warn your users that the URL you provide them is to remain private. Even if Billy 13 year old gets this URL and tries to log in, the only way he can login is through a GET or POST parameter with an rss or atom request. Billy can’t create a cookie with this token and Billy wont have access to anything else on the site, unless you change the above configuration.

Scoping

Scoping with authentication is a little tricky because it can come in many different flavors:

  1. Accounts have many users, meaning users can only belong to one account at a time.

  2. Accounts have and belong to many users, meaning a user can belong to more than one account.

  3. Users access their accounts via subdomains.

  4. Users access their accounts by selecting their account and storing their selection, NOT using subdomains. Maybe you store their selection in a session, cookie, or the database. It doesn’t matter.

Now mix and match the above, it can get pretty hairy. Fear not, because Authlogic is designed in a manner where it doesn’t care how you do it, all that you have to do is break it down. When scoping a session there are 3 parts you might want to scope:

  1. The model (the validations, etc)

  2. The session (finding the record)

  3. The cookies (the names of the session key and cookie)

I will describe each below, in order.

Scoping your model

This scopes your login field validation, so that users are allowed to have the same login, just not in the same account.

# app/models/user.rb
class User < ActiveRecord::Base
  acts_as_authentic :scope => :account_id
end

Scoping your session

When the session tries to validate it searches for a record. You want to scope that search. No problem…

The goal of Authlogic was to not try and introduce anything new. As a result I came up with:

@account.user_sessions.find
@account.user_sessions.create
@account.user_sessions.build
# ... etc

This works just like ActiveRecord, so it should come natural. Here is how you get this functionality:

class Account < ActiveRecord::Base
  authenticates_many :user_sessions
end

Scoping cookies

What’s neat about cookies is that if you use sub domains they automatically scope their self. Meaning if you create a cookie in whatever.yourdomain.com it will not exist in another.yourdomain.com. So if you are using subdomains to scope your users, you don’t have to do anything.

But what if you *don’t* want to separate your cookies by subdomains? You can accomplish this by doing:

ActionController::Base.session_options[:session_domain] = .mydomain.com’

If for some reason the above doesn’t work for you, do some simple Google searches. There are a million blog posts on this.

Now let’s look at this from the other angle. What if you are NOT using subdomains, but still want to separate cookies for each account. Simple, set the :scope_cookies option for authenticate_many:

class Account < ActiveRecord::Base
  authenticates_many :user_sessions, :scope_cookies => true
end

Done, Authlogic will give each cookie a unique name depending on the account.

With the above information you should be able to scope your sessions any way you want. Just mix and match the tools above to accomplish this. Also check out the documentation on Authlogic::ActiveRecord::AuthenticatesMany.

Errors

The errors in Authlogic work JUST LIKE ActiveRecord. In fact, it uses the exact same ActiveRecord errors class. Use it the same way:

class UserSession
  before_validation :check_if_awesome

  private
    def check_if_awesome
      errors.add(:login, "must contain awesome") if  && !.include?("awesome")
      errors.add_to_base("You must be awesome to log in") unless record.awesome?
    end
end

Automatic Session Updating

This is one of my favorite features that I think is pretty cool. It’s things like this that make a library great and let you know you are on the right track.

Just to clear up any confusion, Authlogic does not store the plain id in the session. It stores a token. This token changes with the password, this way stale sessions can not be persisted.

That being said…What if a user changes their password? You have to re-log them in with the new password, recreate the session, etc, pain in the ass. Or what if a user creates a new user account? You have to do the same thing. Here’s an even better one: what if a user is in the admin area and changes his own password? There might even be another place passwords can change. It shouldn’t matter, your code should be written in a way where you don’t have to remember to do this.

Instead of updating sessions all over the place, doesn’t it make sense to do this at a lower level? Like the User model? You’re saying “but Ben, models can’t mess around with sessions and cookies”. True…but Authlogic can, and you can access Authlogic just like a model. I know in most situations it’s not good practice to do this but I view this in the same class as sweepers, and feel like it actually is good practice here. User sessions are directly tied to users, they should be connected on the model level.

Fear not, because the acts_as_authentic method you call in your model takes care of this for you, by adding an after_save callback to automatically keep the session up to date. You don’t have to worry about it anymore. Don’t even think about it. Let your UsersController deal with users, not users AND sessions. ANYTIME the user changes his password in ANY way, his session will be updated.

Here is basically how this is done.…

class User < ActiveRecord::Base
  after_save :maintain_sessions!

  private
    def maintain_sessions!
      # If we aren't logged in at all and the password was changed, go ahead and log the user in
      # If we are logged in and the password has change, update the sessions
    end
end

Obviously there is a little more to it than this, but hopefully this clarifies any confusion. Lastly, this can be altered / disabled via a configuration option. Just set :session_ids => nil when calling acts_as_authentic.

When things come together like this I think its a sign that you are doing something right. Put that in your pipe and smoke it!

Framework agnostic (Rails, Merb, etc.)

I designed Authlogic to be framework agnostic, meaning it doesn’t care what framework you use it in. Right out of the box it supports rails and merb. I have not had the opportunity to use other frameworks, but the only thing stopping Authlogic from being used in other frameworks is a simple adapter. Check out controller_adapters/rails_adapter, or controller_adapters/merb_adapter.

Since pretty much all of the frameworks in ruby follow the Rack conventions, the code should be very similar across adapters. In fact that abstract adapter assumes you are using Rack. If you are using it properly there really isn’t any code you should have to write. Check out the merb_adapter to see for yourself. You’re saying “but Ben, why not just hook into Rack and avoid the need for controller adapters all together?”. It’s not that simple, because rails doesn’t inherit from the Rack::Request class, plus there are small differences between how rack is implemented in each framework. Authlogic has to hook into your controller with a before_filter anyways, so it can “activate” itself. Why not just use the controller object?

The point in all of this rambling is that implementing Authlogic is as simple as creating an adapter. I created both the rails and merb adapters in under 10 minutes. If you have an adapter you created and would like to add please let me know and I will add it into the source.

How it works

Interested in how all of this all works? Basically a before_filter is automatically set in your controller which lets Authlogic know about the current controller object. This “activates” Authlogic and allows Authlogic to set sessions, cookies, login via basic http auth, etc. If you are using your framework in a multiple thread environment, don’t worry. I kept that in mind and made this thread safe.

From there it is pretty simple. When you try to create a new session the record is authenticated and then all of the session / cookie magic is done for you. The sky is the limit.

What’s wrong with the current solutions?

You probably don’t care, but I think releasing the millionth authentication solution for a framework that has been around for over 4 years requires a little explanation.

I don’t necessarily think the current solutions are “wrong”, nor am I saying Authlogic is the answer to your prayers. But, to me, the current solutions were lacking something. Here’s what I came up with…

Generators are messy

Generators have their place, and it is not to add authentication to an app. It doesn’t make sense. Generators are meant to be a starting point for repetitive tasks that have no sustainable pattern. Take controllers, the set up is the same thing over and over, but they eventually evolve to a point where there is no clear cut pattern. Trying to extract a pattern out into a library would be extremely hard, messy, and overly complicated. As a result, generators make sense here.

Authentication is a one time set up process for your app. It’s the same thing over and over and the pattern never really changes. The only time it changes is to conform with newer / stricter security techniques. This is exactly why generators should not be an authentication solution. Generators add code to your application, once code crosses that line, you are responsible for maintaining it. You get to make sure it stays up with the latest and greatest security techniques. And when the plugin you used releases some major update, you can’t just re-run the generator, you get to sift through the code to see what changed. You don’t really have a choice either, because you can’t ignore security updates.

Using a library that hundreds of other people use has it advantages. Probably one of the biggest advantages if that you get to benefit from other people using the same code. When Bob in California figures out a new awesome security technique and adds it into Authlogic, you get to benefit from that with a single update. The catch is that this benefit is limited to code that is not “generated” or added into your app. As I said above, once code is “generated” and added into your app, it’s your responsibility.

Lastly, there is a pattern here, why clutter up all of your applications with the same code over and over?

Limited to a single authentication

I recently had an app where you could log in as a user and also log in as an employee. I won’t go into the specifics of the app, but it made the most sense to do it this way. So I had two sessions in one app. None of the current solutions I found easily supported this. They all assumed a single session. One session was messy enough, adding another just put me over the edge and eventually forced me to write Authlogic. Authlogic can support 100 different sessions easily and in a clean format. Just like an app can support 100 different models and 100 different records of each model.

Too presumptuous

A lot of them forced me to name my password column as “this”, or the key of my cookie had to be “this”. They were a little too presumptuous. I am probably overly picky, but little details like that should be configurable. This also made it very hard to implement into an existing app.

Disclaimer

I am not trying to “bash” any other authentication solutions. These are just my opinions, formulate your own opinion. I released Authlogic because it has made my life easier and I enjoy using it, hopefully it does the same for you.

Copyright © 2008 Ben Johnson of [Binary Logic](www.binarylogic.com), released under the MIT license