Friday, August 26, 2005

Forget Me Not

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:

Thursday, August 25, 2005

TaskTHIS.com

Update: Well, DreamHost wasn't my, uh, dream host for RoR applications. So taskthis.com is down for now. Sorry for any inconvenience this causes. taskthis.darthapo.com is still available.

TaskTHIS.com is alive! TaskTHIS is now being hosted at DreamHost (yes, that link has a referral code in it, every little thing I can do to keep eating.)

DreamHost will be whole lot more stable than my development server. I'm going to retire the old demo instance of TaskTHIS (taskthis.darthapo.com) very soon. But, in the meantime, I have upgraded it to 0.5 so that you can export all of your tasklists and import them into taskthis.com.

I've also created a TaskTHIS development blog at blog.taskthis.com. From this point on, information specific to TaskTHIS will be posted there. My general posts and other Rails posts will still be here.

If you need to move your tasklists from the demo instance to taskthis.com, there's a quick tutorial available.

Technorati Tags:

Sunday, August 21, 2005

TaskTHIS 0.5.1

TaskTHIS 0.5.1 is ready for download. It implements the following:

  • Addresses some PostgreSQL compatibility issues
  • Includes ruby-json dependency in vendor folder
  • New configuration option: send_emails

That's pretty much it. If you have any issues, please feel free to post a comment here and I'll try and address them as soon as I can.

Update: Changed the URL to the download page. The link should work again.

Technorati Tags:

Saturday, August 20, 2005

TaskTHIS 0.5

Taskthis-Logo

TaskTHIS 0.5 is officially released! You can download it from here. I don't have it hosted yet, but I'm working on that. I was able to get the taskthis.com domain name, I just haven't had the time to set it all up yet.

This new release contains:

Taskthis Screenshot

  • Cleaner views (rhtml)
  • Enhanced AJAX support
  • New GUI
  • “Remember Me” login support
  • “Send List To...” -- Allows you to send a list to as many as 10 people via email
  • Public lists (readonly)
  • RSS feeds for public lists
  • Better task item reordering
  • Moved the database schema to a Migration script
  • And much more...

It leverages the following technologies:

It also contains an early release of my theme engine work. Check back soon for an article explaining how I implement themes in Rails applications.

Enjoy!

Update: Updated the URL for downloading the source...

Technorati Tags:

Friday, August 19, 2005

Testicular Abuse

 Sin City

Sin City is a dark, extremely stylized movie. Very noir with a modern twist.

Overall, I like this movie. It is very true to it's source material. However, after watching it again on DVD, I think Frank Miller has some issues that Freud would have had a field day with.

Sunday, August 14, 2005

Do The Math

I read this and laughed. It's actually quite amusing. A guy does the math on why he will never have a girlfriend.