NOTE: This information is mostly outdated, and most links are dead
The login system of this site is based on Rick’s acts_as_authenticated (as people who read some of my previous articles are probably aware of), but it offers no “Remember me” functionality. Translated, when you close your browser, or leave your browser untouched for long enough, you are no longer logged in. This can get tedious, as you can probably understand :)
So I decided to implement this functionality. This is what I looked at, and what I decided to implement.
EDIT: As I explain in this blog entry, the code described in this article is now a part of acts_as_authenticated So unless you have a site that is already using acts_as_authenticated just make sure you got the latest version, and you are all set :)
Sessions, sessions, sessions
acts_as_authenticated uses sessions to remember the user. One way to get rails to remember" the user is to “extend the time a session is kept. Of course, the downside of this is that all sessions become “persistent”, it’s not easy to expire the “remember” state for selected users (manually or scripted), and a host of other consequences.
Hence, I looked elsewhere…
Cookies, lets look at the jar…
Sessions are kept on the server, and a cookie is set in the users browser with a session identifier. This way rails knows what session to load. This implies that if someone else can get a hold of your session id, he can steal your session.
Another way is to create a unique string of yourself, and set a user cookie with said string. Then if you load a page and no user is logged in have your app search for the user with that string, and load a new session for that user. This has some caveats as well, but we’ll address those later.
A cookie to remember…
I ran across this article on how to implement a cookie based “remember me” functionality. It is for Login Engine (BTW, I am not fond of rails engines, but that’s another story ^^), but we’ll “translate” it for acts_as_authenticated.
The login controller
app/controllers/account_controller.rb
excrept:
class AccountController < ApplicationController
def login
return unless request.post?
self.current_user = User.authenticate(params[:login], params[:password])
if current_user
if params[:remember_me] == "1"
self.current_user.remember_me
cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires }
end
redirect_back_or_default(:controller => '/account', :action => 'index')
flash[:notice] = "Logged in successfully"
end
end
def logout
self.current_user.forget_me if current_user
self.current_user = nil
cookies.delete :auth_token
flash[:notice] = "You have been logged out."
redirect_back_or_default(:controller => '/account', :action => 'index')
end
end
The application controller
app/controllers/application.rb
excrept:
class ApplicationController < ActionController::Base
before_filter :login_from_cookie
protected
def login_from_cookie
return unless cookies[:auth_token] && current_user.nil?
user = User.find_by_remember_token(cookies[:auth_token])
if user && !user.remember_token_expires.nil? && Time.now < user.remember_token_expires
user.remember_me
self.current_user = user
cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires }
flash[:notice] = "Logged in successfully"
end
end
end
The model
app/models/user.rb
excrept:
class User < ActiveRecord::Base
def remember_me
self.remember_token_expires = 2.weeks.from_now
self.remember_token = Digest::SHA1.hexdigest("#{salt}--#{self.email}--#{self.remember_token_expires}")
self.password = "" # This bypasses password encryption, thus leaving password intact
self.save_with_validation(false)
end
def forget_me
self.remember_token_expires = nil
self.remember_token = nil
self.password = "" # This bypasses password encryption, thus leaving password intact
self.save_with_validation(false)
end
end
The migration
add_column :users, :remember_token, :string
add_column :users, :remember_token_expires, :datetime