Saturday, 27 March 2010

Authlogic, custom logins and persisting sessions

If you haven't used the authlogic plugin for authentication, you're possibly missing a trick. It's a great piece of code.

However, it's poorly documented for anything out of the well trodden path. Which is a pity because it's so powerful.

Authlogic makes assumptions about how your login facility will work. In particular, it assumes that you will have a login and a password. Which, for more enterprise applications and larger web services, fall short of the mark. Most larger services need a three-way, not two-way authentication. Typically it is customerID, user ID and password. Meaning that user IDs are not unique across customers. Which makes sense given that for most markets, different clients will have the same usernames (e.g. admin, sales, info, etc).

This means that to use authlogic, you need to extend it to use your additional fields.

The easy way to do this is to extend your UserSession class by adding an accessor. For example, we use a company short name as a company ID, so in our case, it's:

class UserSession < Authlogic::Session::Base

attr_accessor :company_sname # Adds company ID to the default Authlogic session fields because this isn't there in standard Authlogic

end

This in turn means that you have to now modify your user_sessions_controller so that you make your logins company-aware:
class UserSessionsController < ApplicationController
  before_filter :require_no_user, :only => [:new, :create]
  before_filter :require_user, :only => :destroy
  protect_from_forgery :except => [:create]

  def index
      render :action => :new
  end

  def new
  end

  def create
    @prevCompSname=params[:user_session]["company_sname"] || ""
    company=Company.find(:all,:conditions => ["lower(short_name) = lower(?)",@prevCompSname]).first
    $companyID=(company&&company.id)||0

    if $companyID > 0
      @user_session = UserSession.new(params[:user_session])
      if @user_session.save
        flash[:notice] = "Login successful!"

        @user_session.user.reset_persistence_token!
        @user_session.save
           
        redirect_to '/mycontroller'
      else
        flash[:error] = "Login failed!"
        redirect_to :action => :show, :controller => :failure_page
      end
    else
      flash[:error] = "Login failed!"
      redirect_to :action => :show, :controller => :failure_page
    end
  end

  def destroy
    current_user_session.destroy
    flash[:notice] = "Logout successful!"
      render :action => :new
  end

end


Unfortunately, there is an obscure bug in authlogic that means that the persistence sometimes breaks. You end up with an attempt to select from your user table with perishable_token=1 instead of a string, which results in an SQL error and the login failing.

Some people have found work-arounds to this by unpacking the gem. However, a more reliable fix that we found was to simply explicitly call the method to force a persistent token in the User model:

@user_session.user.reset_persistence_token!

and then to make sure that this persistence token is saved for the sesssion, you have to call the session save:
@user_session.save

This then gives you a persistence token that is saved across the sessions as it should be.

No comments:

Post a Comment