Search code examples
ruby-on-railsauthenticationcookiesremember-me

Automatic Login with Rails?


I am trying to get up a simple authentication system with Rails' Restful-Authentication plugin, and am just wondering how it works, b/c I can't seem to figure out what the requirements are for cookies, and how to make it so the browser always remembers you (for 6+ months).

Few questions:

1) How do you do remember_me's for ruby's restful_authentication? I can't seem to find a good one-liner to solve this problem...

If a user signs up and checks "Remember Me", how does the rails application get the session/cookie without the user doing anything but going to the page the next time they go to the page, say 3 months later?

2) Do I need to send some sort of info to the server, like their IP address or something? What is cookies[:auth_token], where is that defined?

The goal is: I don't want them to have to enter their email/password again, like how StackOverflow works :)


Solution

  • Here's what we're doing (largely taken from authenticated system) ... this is the controller method that handles login that we're running...

    def login
      if logged_in?
        flash[:notice] = "You are already logged in."
        redirect_to "/" and return
      end
      unless request.post?
        render :layout => 'task' and return
      end
      self.current_user = User.authenticate(params[:login], params[:password])
      if logged_in?
        if params[:remember_me].to_i == 1
          self.current_user.remember_me
          cookies[:auth_token] = {:domain => "#{DOMAIN}", :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
        else
          self.current_user.forget_me
          cookies.delete(:auth_token, :domain => "#{DOMAIN}")
          cookies[:auth_token] = nil
        end
        current_user.last_seen_at = Time.now 
        current_user.save
        session[:notice] = "You logged in successfully"
        flash[:notice] = "You logged in successfully"
        redirect_back_or_default(:controller => 'dashboard') and return
        #redirect_back_or_default(:controller => 'index', :action => 'index') and return
      else
        if $failed_login_counter.add_attempt(params[:login]) > MAXIMUM_LOGIN_ATTEMPTS
          logger.info("login rate limiter kicking in, #{MAXIMUM_LOGIN_ATTEMPTS} login attempts failed")
          redirect_to "/denied.html" and return
        end
        flash[:error] = "Unable to authenticate username and password"
        render(:layout => 'task') and return
      end
    end
    

    And use this for logout

    def logout
      current_user.last_seen_at = Time.now 
      current_user.save
      self.current_user.forget_me if logged_in?
      cookies.delete(:auth_token, :domain => "#{DOMAIN}")
      reset_session
      flash[:notice] = "You have been logged out."
      #redirect_to :back
      redirect_back_or_default(:controller => 'index', :action => 'index') and return
    end
    

    Then - in your application.rb you'll need something like:

    before_filter :login_from_cookie
    
    def login_from_cookie
      return unless cookies[:auth_token] && !logged_in?
      user = User.find_by_remember_token(cookies[:auth_token])
      if user && user.remember_token?
        user.remember_me
        self.current_user = user
        cookies[:auth_token] = { :domain => "#{DOMAIN}", :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
        flash[:notice] = "#{self.current_user.login}, you have logged in successfully"
      end
    end
    

    And - in your User model have some methods like this:

    # Encrypts some data with the salt.
    def self.encrypt(password, salt)
      Digest::SHA1.hexdigest("--#{salt}--#{password}--")
    end
    
    # Encrypts the password with the user salt
    def encrypt(password)
      self.class.encrypt(password, salt)
    end
    
    def remember_token?
      remember_token_expires_at && Time.now.utc < remember_token_expires_at 
    end
    
    # These create and unset the fields required for remembering users between browser closes
    def remember_me
      self.remember_token_expires_at = 2.weeks.from_now.utc
      self.remember_token            = encrypt("#{email}--#{remember_token_expires_at}")
      save(false)
    end
    
    def forget_me
      self.remember_token_expires_at = nil
      self.remember_token            = nil
      save(false)
    end