For TaskTHIS, I’ve implemented a simple authentication cache. The “Remember me…” checkbox on the login form. I’m going to present to you, good or bad, how I decided to design it.
After looking at how it’s done in other technologies and how secure (or actually insecure) it is, I decided to try something a little different. My goals were to 1) avoid setting cookies with any personal information, and 2) make it perishable. Meaning, I wanted the cookie value itself to expire.
High Level
When “Remember Me” is selected on the login screen the application creates a unique token and saves it into a cookie. The token is called a remembrall. The remembrall is essentially an SHA hash of the user email and the exact time the remembrall was created. In addition, the application also generates an expiration date for the remembrall. So it’s only a valid token for a short while, satisfying my second goal.
It may appear that I have already broken my first goal, I have used the user’s email address in the token. Looking back, I probably should have used the USER
record’s ID
(usually just an integer that’s unique within the system) as the information that’s unique to the user. However, since it’s hashing the email and timestamp, I believe it satisfactorily meets the first goal.
At this point, we need a way to recognize the remembrall on pages that are protected, and automatically log the user into the system if the remembrall is still ‘fresh’. To accomplish this, TaskTHIS uses a filter that runs before every page is loaded. The filter checks to see if there’s a remembrall set and if it’s valid. The flow looks a little something like this:
- Is there a remembrall cookie set? Yes…
- Is there a currently logged in user? No…
- Is there a user in the system with this remembrall? Yes…
- Has the remembrall expired? No…
- Log the user in!
Whew, that was a mouthful.
Less High Level
So let’s look at some code, eh?
The initial user authentication was created with the excellent login_generator. What follows build upon that foundation.
We need to modify the User so that the remembrall can be saved. So, in Migration format, the user gets:
add_column :users, :remembrall, :string, :length=>40
add_column :users, :remembrall_expires, :datetime
One of the results of having an automatic login process is that the login procedure can be called from two different places. Each with a different destination after the successful login. What does that mean? It means I needed to abstract the code for logging in a user. For TaskTHIS, I put that into the AutoLogin module. I wouldn’t really recommend handling it this way, in the future TaskTHIS won’t. I think it should really be a protected method in your ApplicationController
.
In the login process, TaskTHIS keeps track of the last time a user logs in. Also, each user’s settings are stored as a YAML string. The login code needs to handle all of this, the code looks like this:
def handle_login( user )
@session[:user] = user
@session[:user_prefs] = YAML::load(user.prefs)
user.last_login = Time.now
user.save
end
The following is the filtering code used with :before_filter
in the ApplicationController
:
def login_from_cookie
user = User.find_by_remembrall( cookies[:remembrall] ) if cookies[:remembrall] and @session[:user].nil?
if user and active_remembrall? user
handle_login user
end
end
It’s using a helper method in the same module for testing the remembrall timestamp:
def active_remembrall?( user )
return Time.now < user.remembrall_expires unless user.remembrall_expires.nil?
false
end
From here, it’s just a matter of hooking it all up. In the
, we’ll need to make sure it calls our custom handle_login
method. For clarity, here’s the entire login method:
def login
case @request.method
when :post
if user = User.authenticate(@params[:user_login], @params[:user_password])
handle_login( user )
if @params[:remember_me]
create_remembrall( current_user )
end
flash['notice'] = “Login successful”
redirect_back_or_default :action => “welcome”
else
flash.now['notice'] = “Login unsuccessful”
@login = @params[:user_login]
end
end
end
You’ll notice that it uses some things we haven’t discussed: currentuser
and createremembrall
. They can be found in the lib/auto_login.rb
file.
Now, in our ApplicationController
, we’ll need to add the call for the :before_filter
:
class ApplicationController < ActionController::Base
include LoginSystem
include AutoLogin
before_filter :login_from_cookie
# the handle_login method and other stuff here...
end
So that, in a nutshell, is how TaskTHIS handles cached user authentication. I’ve not shown all of the code. For example, I didn’t show the code that actually creates the remembrall. For all of that, you can download the code and have a look for yourself.
Let me know what you think or what you would have done differently.
Technorati Tags: rails