mattmccray

 
« TaskTHIS.com | Home | Rails on Windows (and… »

Forget Me Not

» Published August 26, 2005 under Rails, Software

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

 Gallery Data Thumbnails 3 Psss82-1

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:

TaskTHIS Autologin Process

  1. Is there a remembrall cookie set? Yes...
  2. Is there a currently logged in user? No...
  3. Is there a user in the system with this remembrall? Yes...
  4. Has the remembrall expired? No...
  5. 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 Controller::login, 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:


9 comments

03.17.06 @ 22:13 Andrew said...
Yeah I can’t get this to work at all cause you please provide complete code for this to work 100% correctly cause the link you have on this page for the code does not work!
04.09.06 @ 19:14 Micah Fitch said...
Andrew, I don’t know ifi you read the beginning, but he said that he used Login_Generator for the initial code!
04.12.06 @ 14:08 said...
Forget Me Not at M@ McCray…

For TaskTHIS, Ive implemented a simple authentication cache. The Remember me checkbox on the login form. Im going to present to you, good or bad, how I decided to design it….
08.30.06 @ 16:16 Shanti Braford said...
I’m sure you know this, but the code sections of the site are no longer highlighted / readable.

Switched blogging software I take it =)
08.31.06 @ 04:09 M@ said...
Yep, you guessed it. I changed away from WordPress.
04.25.08 @ 10:38 bunnyhero said...
here’s a well-thought-out article about persistent logins and best practices: http://fishbowl.pastiche.org/2004/01/19/..
05.07.10 @ 12:12 AngeliqueFORD30 said...
Buildings are expensive and not everybody can buy it. However, http://lowest-rate-loans.com was created to support different people in such kind of hard situations.
05.10.10 @ 23:19 mbt shoes said...
As technology develops, people’s living standards improve, people increasingly value the quality of life, of course, this also includes food and clothing and so on. This is mainly manifested in the form of the stress on the wear is, such as shoes: we all have hobbies, pursue their favorite brand-name products. Some people like to wear mbt shoes, some people like to wear Nike shoes and so on. For different occasions, people have very strict requirements for shoes. For example: If you are playing basketball, then you have to wear regular mbt shoes. If you want to run, then you have to wear running shoes.
07.02.10 @ 20:57 vdz said...
adfa
[url http://www.adasd.com]adf[/url]adf[/url]
sadf

No trackbacks

Trackback link:

Please enable javascript to generate a trackback url


You may use Textile, or simple html tags (B,I). Feel free to use Emoticons too. Oh, and please limit yourself to only five links per comment. Anything more and you'll probably get detained by the spam police.