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"
and 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 => user_session_path do |f|
-#= form_for @user_session, :url => {:controller => :user_sessions, :method => :post} do |f|
= 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.