Auto-login 28

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

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

Leave a response

  1. Tom Riley Fri, 03 Mar 2006 15:51:43 GMT
    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 Sat, 04 Mar 2006 08:11:10 GMT
    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. Fri, 21 Apr 2006 06:38:52 GMT
    Bla bla bla
  4. Rafael Lima Tue, 25 Apr 2006 18:46:45 GMT
    Great! I'm using the Login Engine Plugin too. I will try your code... Thanks a lot!
  5. Yardboy Sat, 06 May 2006 10:07:53 GMT
    Appreciate you posting this, helped me a ton - nice work!
  6. Problem Solvings Skills Thu, 18 May 2006 18:00:11 GMT
    Very nice. Is the changes made to the original login engine source?
  7. jungly Fri, 19 May 2006 14:44:33 GMT
    saved my day too. muchos gracias
  8. Daniel Wanja Fri, 19 May 2006 15:10:10 GMT
    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 Sun, 28 May 2006 18:47:46 GMT
    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 Sun, 28 May 2006 21:10:27 GMT
    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 Tue, 06 Jun 2006 07:03:25 GMT
    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 Mon, 19 Jun 2006 18:50:25 GMT
    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 Wed, 18 Oct 2006 07:09:36 GMT

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

  14. dude Wed, 10 Jan 2007 02:23:06 GMT

    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 Wed, 10 Jan 2007 02:24:18 GMT

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

  16. Daniel Wanja Fri, 12 Jan 2007 09:16:21 GMT

    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 Sun, 18 Feb 2007 12:11:15 GMT

    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 Sun, 18 Feb 2007 12:11:29 GMT

    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 Wed, 21 Feb 2007 13:40:49 GMT

    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 Tue, 27 Feb 2007 13:48:06 GMT

    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 Sat, 09 Jun 2007 23:47:53 GMT

    I am new to ror

    very nice article…thanks

  22. hexcatalyst@gmail.com Wed, 13 Jun 2007 03:04:15 GMT

    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 Wed, 13 Jun 2007 06:04:24 GMT

    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 Wed, 13 Jun 2007 15:28:26 GMT

    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 the login_engine from the latest release.

    Good luck.

    -Lee

  25. nitin@vinfotech.com Fri, 13 Jul 2007 06:07:45 GMT

    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 Sun, 07 Oct 2007 11:24:38 GMT

    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 Sat, 08 Dec 2007 05:07:50 GMT

    so much thanks

  28. Jill Mon, 05 May 2008 08:06:33 GMT

    that was a helpful hint in solving the problem…

Comments