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') endend
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 endend
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) endend
The migration
add_column :users, :remember_token, :stringadd_column :users, :remember_token_expires, :datetime