Authenticated Cookie == Safe && Fast
Posted by Mathew Abonyi Sat, 29 Jul 2006 09: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:
- Install Acts as Authenticated
- Create User model
- Fix application to expect current_user.is_a? User
- (optional) Fix application to not use sessions or flash
- Remove “include AuthenticatedSystem” from application.rb
- Add migration from below
- Place the following library in your lib and require it in environment.rb
- Put include AuthenticatedCookie in your application.rb
- (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
endAddLoginKeysToUsers 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
endAuthenticated 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
endPermanency (“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.
