If you want to get authlogic working in a fresh rails 3 project, it will take a bit more steps than devise. This has everything to do with vision: authlogic only claims to deliver you the backend, allowing itself to remain more stable and to easily replace other authentication libraries (even your home-brewn). That seems a great philosophy, but starting from scratch involves more steps.

First you need to setup a clean rails3 project, as i described before.

Install the gem

As simple as adding the following line to your Gemfile :

gem "authlogic"
gem "rails3-generators"

(note: you need the rails3-generators to include the needed generators).

Then run bundle install or bundle update.

Create the UserSession

rails g authlogic:session UserSession

This would be all, but in my installation it was not enough. Something inside rails3 broke the authlogic session.
But the fix, luckily, is pretty easy: you have to add the to_key function.

So your complete UserSession model will look as follows:

class UserSession < Authlogic::Session::Base

  def to_key
    new_record? ? nil : [ self.send(self.class.primary_key) ]
  end

end

Create the User

If you do not yet have a User model, and i am assuming you don’t, you need to

rails g model User

For now, this is an empty model. You will need to fill in your migration to create the model correctly.

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string    :login,               :null => false
      t.string    :email,               :null => false
      t.string    :crypted_password,    :null => false
      t.string    :password_salt,       :null => false
      t.string    :persistence_token,   :null => false
      #t.string    :single_access_token, :null => false                # optional, see Authlogic::Session::Params
      #t.string    :perishable_token,    :null => false                # optional, see Authlogic::Session::Perishability

      # magic fields (all optional, see Authlogic::Session::MagicColumns)
      t.integer   :login_count,         :null => false, :default => 0
      t.integer   :failed_login_count,  :null => false, :default => 0
      t.datetime  :last_request_at
      t.datetime  :current_login_at
      t.datetime  :last_login_at
      t.string    :current_login_ip
      t.string    :last_login_ip
      t.timestamps
    end

    add_index :users, ["login"], :name => "index_users_on_login", :unique => true
    add_index :users, ["email"], :name => "index_users_on_email", :unique => true
    add_index :users, ["persistence_token"], :name => "index_users_on_persistence_token", :unique => true

  end

  def self.down
    drop_table :users
  end
end

Now we still have to add some code to the User model :

class User < ActiveRecord::Base
  acts_as_authentic
end

ApplicationController

We need to add some generic code to our applicationcontroller to persist the sessions, to check whether a user is required and do the correct redirects when needed.

class ApplicationController < ActionController::Base
  protect_from_forgery

  helper_method :current_user_session, :current_user

  private
    def current_user_session
      logger.debug "ApplicationController::current_user_session"
      return @current_user_session if defined?(@current_user_session)
      @current_user_session = UserSession.find
    end

    def current_user
      logger.debug "ApplicationController::current_user"
      return @current_user if defined?(@current_user)
      @current_user = current_user_session && current_user_session.user
    end

    def require_user
      logger.debug "ApplicationController::require_user"
      unless current_user
        store_location
        flash[:notice] = "You must be logged in to access this page"
        redirect_to new_user_session_url
        return false
      end
    end

    def require_no_user
      logger.debug "ApplicationController::require_no_user"
      if current_user
        store_location
        flash[:notice] = "You must be logged out to access this page"
        redirect_to account_url
        return false
      end
    end

    def store_location
      session[:return_to] = request.request_uri
    end

    def redirect_back_or_default(default)
      redirect_to(session[:return_to] || default)
      session[:return_to] = nil
    end
end

UserSessionsController

rails g controller UserSessions new

and fill the controller with the correct code:

class UserSessionsController < ApplicationController
  before_filter :require_no_user, :only => [:new, :create]
  before_filter :require_user, :only => :destroy

  def new
    @user_session = UserSession.new
  end

  def create
    @user_session = UserSession.new(params[:user_session])
    if @user_session.save
      flash[:notice] = "Login successful!"
      redirect_back_or_default users_url
    else
      render :action => :new
    end
  end

  def destroy
    current_user_session.destroy
    flash[:notice] = "Logout successful!"
    redirect_back_or_default new_user_session_url
  end
end

and of course that also needs a view, in new.html.haml

%h1 Login

= form_for @user_session, :url => {:action => "create"} do |f|
  = f.error_messages
  %div
    = f.label :login
    = f.text_field :login
  %div
    = f.label :password
    = f.password_field :password
  %div
    = f.check_box :remember_me
    = f.label :remember_me
  %div
    = f.submit "Login"

We want to use the f.error_message, but that is now removed from Rails3 and we need to install a plugin instead:

rails plugin install git://github.com/rails/dynamic_form.git

We also need to define, inside config/routes.rb. You will see that the generator will have added the route get 'user_sessions/new', but that is not enough.

You will have to add:

  resources :user_sessions

  match 'login' => "user_sessions#new",      :as => :login
  match 'logout' => "user_sessions#destroy", :as => :logout

Restrict access

Suppose you now have some other controller, e.g. HomeController, then restricting acces is straightforward:

rails g controller home index
class HomeController < ApplicationController

  before_filter :require_user

  def index
  end

end

The method require_user, defined in ApplicationController, will check if there is a user logged on, and if not redirect to the login-page.

If we now delete the index.html inside your public folder, and we add the following at the bottom of config/routes.rb

root :to => 'home#index'

Now we can start our application, and it should redirect to our login-page.

But of course, since we have no users for now, we can’t login just yet. To allow testing, you fire up your console:

$ rails c
Loading development environment (Rails 3.0.0.rc)
ruby-1.9.2-p0 > User.create(:login => 'test', :email => 'test@tester.com', :password => 'test123', :password_confirmation => 'test123')
 => #<User id: 1, login: "test", email: "test@tester.com", crypted_password: "0129d9733b7912017e37a50263901488da90e127e6fd1ae6081...", password_salt: "ygufdflWkJQZGhbsGyia", persistence_token: "957788609b2067092dd3852b01613d53f96d0692ce5131174ca...", login_count: 0, failed_login_count: 0, last_request_at: nil, current_login_at: nil, last_login_at: nil, current_login_ip: nil, last_login_ip: nil, created_at: "2010-08-29 14:11:04", updated_at: "2010-08-29 14:11:04">
ruby-1.9.2-p0 >

Now you should be able to login using this user.

For completeness, you should add some links to your application-view to allow logging in and out:

      #user_nav
        - if current_user
          = "Signed in as #{current_user.email}. Not you?"
          = link_to "Sign out", logout_path
        - else
          = link_to "Sign in", new_user_session_path

This would get your rails3 project started.

The next steps would be to add some user management, or allowing users to sign up themselves, and maybe add some roles to limit certain users access if needed.

Now, to contrast this with devise: i now have a bunch of code in my application that actually is not specific to my code, but is also completely not tested. I will provide example rspec tests for this later.

Actually, the problem with this scenario as outlined, is it is one big bang. You should start little, declaring things you need to be able to do, and work in little steps to get there. But this scenario is intended as a draft to see how you could get there. Now, in your real application, you should start with the tests, and then from this example you now how you can implement it.