Sat, 18 Feb 2006 21:41:00 GMT

Auto-login

One of my midnight Rails projects is a “time tracking” application for which I needed auto-login. You know, the “Remember me” check box so that you don’t have to login each time you visit the application. I found a nice article written by Matt McCray describing how this was implemented for TaskThis.com at http://www.mattmccray.com/archives/category/software/rails/taskthis/. Even further he provides the full source code for the application. I didn’t take directly his auto_login.rb module but was greatly inspired by it. I also used the Login Engine Plugin that was not providing this feature, maybe this changed, so it could be simpler, but how simple implementing the auto-login can be. Note these are not the full classes just pertinent code extracts.

1. Remember me

When the user login and checks the “Remember me” checkbox, the :save_login parameter is set, the User instance remember_me method invoked and the :auth_token cookie set.

class AccountController < ApplicationController
  def login
    case @request.method
      when :post
      if @session[:user] = User.authenticate(@params[:user_login], @params[:user_password])
        flash['notice']  = "Login successful"
        if @params[:save_login] == "1"
          @session[:user].remember_me
          cookies[:auth_token] = { :value => @session[:user].remember_token , :expires => @session[:user].remember_token_expires }
        end
        redirect_back_or_default :controller => "time"
      else
        flash.now['notice']  = "Login unsuccessful"
        @login = @params[:user_login]
      end
    end
  end
  
  def logout
    @session[:user].forget_me if @session[:user]
    @session[:user] = nil
    cookies.delete :auth_token
  end
end

2. login_from_cookie

The next time the user visits the website the “login_from_cookie” filter is triggered. This method checks that the user is not logged in and that the :auth_token cookie is set. If that’s the case the user matching the :auth_token is searched and the token_expiration verified the the user is automatically logged in. Et voila!
I guess auto_login would be more appropriate as method name.

class ApplicationController < ActionController::Base
   before_filter :login_from_cookie
   def login_from_cookie
      return unless cookies[:auth_token] && @session[:user].nil?
      user = User.find_by_remember_token(cookies[:auth_token]) 
      if user && !user.remember_token_expires.nil? && Time.now < user.remember_token_expires 
         @session[:user] = user
      end
   end
end

3. the User class

The User class has two methods to set and remove the token from the database. It’s pretty secure as from the token the user cannot be identified without having the salt, the email, and the token expiration, which is most unlikely to be recreated. It could be even more secure by just encrypting some random unique identifier. The only issue I encountered was that the user class always forces the password validation and encryption when saving. For now I just bypass validation and encryption when setting and clearing the remember_me token.

class User < ActiveRecord::Base
  def remember_me
    self.remember_token_expires = 2.weeks.from_now
    self.remember_token = Digest::SHA1.hexdigest("#{salt}--#{self.email}--#{self.remember_token_expires}")
    self.password = ""  # This bypasses password encryption, thus leaving password intact
    self.save_with_validation(false)
  end
  
  def forget_me
    self.remember_token_expires = nil
    self.remember_token = nil
    self.password = ""  # This bypasses password encryption, thus leaving password intact
    self.save_with_validation(false)
  end
end

Comments

  • Tom Riley says
    Awesome! You just saved me from hacking around with rails session settings - yours is a much better solution. I managed to plug it into my modified copy of the login engine with very few modifications. This code should certainly be rolled into the engine. Thanks again!
  • Calvin Yu says
    Thank you for the post - I was able to use the same code for the SaltedHashLoginGenerator with only a couple of minor tweaks.
  • T. T. P. says
    Bla bla bla
  • Rafael Lima says
    Great! I'm using the Login Engine Plugin too. I will try your code... Thanks a lot!
  • Yardboy says
    Appreciate you posting this, helped me a ton - nice work!
  • Problem Solvings Skills says
    Very nice. Is the changes made to the original login engine source?
  • jungly says
    saved my day too. muchos gracias
  • Daniel Wanja says
    Glad it worked for you. I haven't added the change to the original login engine as I still added more changes to the version I used so it's not too generic anymore. One of the changes was not to store the User itself in the session, but only the user id.
  • albus522 says
    This only has one flaw that I can see. That is that you are limited to one computer for autologin. If you choose remember me on another computer the hash will be changed and when you go back to the original computer you will not be logged in.
  • Daniel Wanja says
    Correct. This is a flaw when login from two different computers the second login would override the token of the first login. This can be fixed by testing if a token is already set on the user and re-use the existing token. Hence the same token would be valid from two different pc. I believe Lee fixed this on time.onrails.org as this was really was enoying him.
  • freetwix says
    hey, the code beneath will resolve any problems with the password (in my case) and do not need any hacks. by the way, thanks for the article, daniel.

    def remember_me
    update_attributes(
    :remember_token_expires => 2.weeks.from_now,
    :remember_token => Digest::SHA1.hexdigest("#{salt}--#{self.email}--#{self.remember_token_expires}"))
    end

  • Daniel Wanja says
    On a new Rails project I am working on we are using the acts_as_authenticated which I quite like. See http://technoweenie.stikipad.com/plugins/show/Acts+as+Authenticated for more info. I also stumbled upon a detailed article at http://www.aidanf.net/rails_user_authentication_tutorial.
  • Steve Jernigan says

    Anyone else had/having a problem with Safari and this solution? Any fixes?

  • dude says

    this website looks all messed up when it renders on Internet Explorer 1.6 and slightly less ( but still messed up) on firefox 2.0

  • dude says

    i mean this website! not the code that is being given. The code I haven’t tried, but probably works.

  • Daniel Wanja says

    Thanks for the feedback on how this site renders in IE. You are correct, it’s a little messed up. It’s even worth in IE7. We are currently moving this blog to media template. During that process I will change the template to work as well on IE. Note this may also explain why only 15.41% of reader of this blog are using IE while 66.76 are using Firefox and 10.27% are using Safari.

  • Niall Doherty says

    I had some difficulty getting this to work with the SaltedHashLoginGenerator, but finally got it. Turns out you need to create two extra columns in your users table (or equivalent).

    remember_token_expires … datetime
    remember_token … varchar(40)

    Thanks for this, Daniel.

  • Niall Doherty says

    I had some difficulty getting this to work with the SaltedHashLoginGenerator, but finally got it. Turns out you need to create two extra columns in your users table (or equivalent).

    remember_token_expires … datetime
    remember_token … varchar(40)

    Thanks for this, Daniel.

  • ngw says

    Hi, I’m trying to write a functional test for this, but I’m not able to make it work …

    def test_authentication_with_cookie post :login, { :username => ‘foo’, :password => ‘passwd’, :remember_me => ‘1’ } user = User.find(:first, session[:user_id]) puts cookies[‘auth_token’].inspect assert_equal cookies[‘auth_token’].value, user.remember_token assert_equal cookies[‘auth_token’].expires, user.remember_token_expiration end

    How do you test it ?

  • Daniel Wanja says

    Hi John,

    If you are talking about time.onrails.org just try to enter any password and press the login button, then a ‘forgot password’ link will appear and your password will be emailed to you.

  • srinu_s@yahoo.com says

    I am new to ror

    very nice article…thanks

  • hexcatalyst@gmail.com says

    Hi,
    I’m using login engine as my plugins on rails.

    I cerated model and controller then I follow the instructions and added user in the database.

    When I reload the page: “we’re sorry something went wrong….”

    Can anyone help me? please email me.
    any help would be greatly appreciated

    Thanks

  • hexcatalyst@gmail.com says

    when i put this to environment.rb

    module LoginEngine config :salt, “your-salt-here” end

    Engines.start :login


    i get problem when starting up the server.

  • Lee says

    hexcatalyst,

    It sounds like you’re having an issue specific to the login_engine from the rails engine plugin. We don’t use that plugin, so you may want to check with their support area for help. It looks like the rails engine plugin might have even dropped"login_engine the login_engine from the latest release.

    Good luck.

    -Lee

  • nitin@vinfotech.com says

    hi,
    I am a newbie cum naive to programming. But intrested in developing the Autologin feature for IE/Firefox/ basically windows base in C#. Can anybody guide me……..plz

  • top online poker casinos says

    Type curtsied that online poker assistant. A action is diabolically overall. I lent that play internet poker online away from some online poker assistant. Some Learn to Play Poker is ferociously agreed. Some section is embarrassingly excess. Some missing online poker assistant was above the established report. In my opinion, this community is less favourable than an essential room. View fidgeted some Learn to Play Poker. It’s romantic to be mislaid! It’s vocational to be directed! It’s driving to be taped! According to common sense, one Learn to Play Poker is far more gothic than the meaningful approach…

  • aageboi says

    so much thanks

  • steve says

    You can also check this out, Really Simple Remember Me’s. http://www.thewojogroup.com/2008/09/remember-mes-with-rails/

  • Brett says

    Nice function. I wish I would have found this article earlier, the use of an auth_token for the the session variable makes it much easier. I using up doing this:

    http://www.thewojogroup.com/2008/09/remember-mes-with-rails/

    haha exactly what the guy above posted.

  • Eivind says

    I made a small enhancement (IMO).

    If the user logs in with the “remember me” option UNSET, then I destroy the token and delete the cookie. I think this is what the user will expect, as otherwise it could leave in place a previous token/cookie and subsequent auto-login will work.

    I realize that the logout function does that as well.