Authenticated Cookie == Safe && Fast

Posted by Mathew Abonyi Sat, 29 Jul 2006 08:28:39 GMT

I don’t really think this is deserving of a plugin, but for those of you using Acts as Authenticated who want to really squeeze twice as much out of your application, here’s one way to do it:

  1. Install Acts as Authenticated
  2. Create User model
  3. Fix application to expect current_user.is_a? User
  4. (optional) Fix application to not use sessions or flash
  5. Remove “include AuthenticatedSystem” from application.rb
  6. Add migration from below
  7. Place the following library in your lib and require it in environment.rb
  8. Put include AuthenticatedCookie in your application.rb
  9. (optional) Turn off sessions by placing the following in your environment.rb:
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.update(:disabled => true)

Additions to User model

  def self.find_by_key(key, options = {})
    find_by_login_key(key, options.merge(:conditions => ["login_key_expires_at > ?", Time.now]))
  end

  def self.clear_key(key, options = {})
    if u = find_by_login_key(key, options)
      u.update_attributes(:login_key => nil, :login_key_expires_at => nil)
    end
  end

  def create_login_key
    self.login_key = Digest::SHA1.hexdigest( Time.now.to_s.split('//').sort_by {rand}.join )
    self.login_key_created_at = Time.now
    self.login_key_expires_at = 1.week.from_now
    self.save
  end

AddLoginKeysToUsers Migration

class AddLoginKeyToUsers < ActiveRecord::Migration
  def self.up
    #                   column                  type        options
    add_column  :users, :login_key,             :string,    :default => nil
    add_column  :users, :login_key_created_at,  :datetime,  :default => nil
    add_column  :users, :login_key_expires_at,  :datetime,  :default => nil
    add_index   :users, :login_key
  end

  def self.down
    remove_index :users, :login_key
    remove_column :users, :login_key_expires_at
    remove_column :users, :login_key_created_at
    remove_column :users, :login_key
  end
end

Authenticated Cookie Library

# AuthenicatedCookie
# A hybrid of AAA and my own ideas
#
module AuthenticatedCookie

  def self.included(base)
    base.send :helper_method, :logged_in?, :current_user
    base.send :before_filter, :login_from_cookie
  end

  public

  # Login Control

  def login_from_cookie
    self.current_user ||= cookie_exists? ? load_cookie : nil
  end

  def login_required
    logged_in? ? access_granted : access_denied
  end

  def logged_in?
    self.current_user.is_a? User
  end

  # Current User Control

  def current_user=(new_user)
    create_cookie(new_user) if new_user.is_a?(User) && !cookie_exists?
    destroy_cookie if new_user.nil? && cookie_exists?
    @current_user = new_user
  end

  def current_user
    @current_user
  end

  # Access Responses

  def access_granted
    true
  end

  def access_denied
    store_location
    redirect_to(login_url)
    false
  end

  # Redirection Control

  def store_location
    cookies[:return_to] = { :value => request.request_uri, :expires => 1.hour.from_now }
  end

  def redirect_back_or_default(default)
    return_to = cookies[:return_to] || default
    cookies.delete(:return_to) if cookies[:return_to]
    redirect_to return_to
  end

  def redirect_back
    redirect_back_or_default(index_url)
  end

  # Cookie CRUD

  def auth_cookie
    cookies[:ack]
  end

  def cookie_exists?
    !auth_cookie.nil?
  end

  def create_cookie(user)
    if user.create_login_key
      cookies[:ack] = { :value => user.login_key, :expires => user.login_key_expires_at }
    end
  end

  def destroy_cookie
    User.clear_key(auth_cookie) and cookies.delete(:ack)
  end

  def load_cookie
    if cookie_exists?
      u = User.find_by_key(auth_cookie)
      cookies.delete(:ack) unless u
    end
    u ? u : nil
  end

end

Permanency (“Remember Me”)

The implementation is a trifle. For the User model, add a custom expiry time to create_login_key which is much longer if the user wants to be remembered. Add ‘attr_accessor :remember’ and ‘def remember?; @remember != “0”; end’ to User and add a f.check_box :remember to your login view. Be sure to pass the value of :remember from new users to authenticated users. That’s all.

Explanation of Authenticated Cookie

Before anyone bulks at this form of authentication, take just a second to think what storing a session ID is doing… that’s right. The same bloody thing. So why waste so much memory, CPU, table space and requests/sec to reprocess whether someone is logged in.

I included a rudimentary redirect to show that the cookie is just as good as sessions for redirection. You could extend this concept to flash, preloaded preferences, etc.

However, this is not evangilism against sessions. I think they’re great to have, but they aren’t the right tool and have been misused, in my opinion, far too much. Cookies offer a tainted store. Tainted information should go in cookies and be considered tainted. Sessions are and should be treated as a secure store. Thing is, if you are following CRUD design, there’ll be very little for you to secure and keep untainted which isn’t validated and in a database.

Posted in  | no comments | no trackbacks

Comments

Trackbacks

Use the following link to trackback from your own site:
http://www.mathewabonyi.com/articles/trackback/15

(leave url/email »)

   Comment Markup Help Preview comment