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

Posted by Daniel Wanja Sat, 18 Feb 2006 13:41:00 GMT


Comments

  1. Tom Riley 13 days later:
    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!
  2. Calvin Yu 14 days later:
    Thank you for the post - I was able to use the same code for the SaltedHashLoginGenerator with only a couple of minor tweaks.
  3. T. T. P. 2 months later:
    Bla bla bla
  4. Rafael Lima 2 months later:
    Great! I'm using the Login Engine Plugin too. I will try your code... Thanks a lot!
  5. Yardboy 3 months later:
    Appreciate you posting this, helped me a ton - nice work!
  6. Problem Solvings Skills 3 months later:
    Very nice. Is the changes made to the original login engine source?
  7. jungly 3 months later:
    saved my day too. muchos gracias
  8. Daniel Wanja 3 months later:
    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.
  9. albus522 3 months later:
    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.
  10. Daniel Wanja 3 months later:
    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.
  11. freetwix 4 months later:
    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

  12. Daniel Wanja 4 months later:
    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.
  13. Steve Jernigan 8 months later:

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

  14. dude 11 months later:

    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

  15. dude 11 months later:

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

  16. Daniel Wanja 11 months later:

    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.

  17. Niall Doherty 12 months later:

    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.

  18. Niall Doherty 12 months later:

    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.

  19. ngw about 1 year later:

    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 ?

  20. Daniel Wanja about 1 year later:

    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.

  21. srinu_s@yahoo.com over 1 year later:

    I am new to ror

    very nice article…thanks

  22. hexcatalyst@gmail.com over 1 year later:

    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

  23. hexcatalyst@gmail.com over 1 year later:

    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.

  24. Lee over 1 year later:

    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

  25. nitin@vinfotech.com over 1 year later:

    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

  26. top online poker casinos over 1 year later:

    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…

  27. aageboi almost 2 years later:

    so much thanks

  28. steve over 2 years later:

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

  29. Brett over 2 years later:

    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.

  30. Eivind about 3 years later:

    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.