on Mar 8th, 2006How to persist Rails sessions via cookies using the Login Engine plugin
I absolutely can't stand having to log in to web applications on every visit. If I manage to remember what login I used, I'm not going to remember the password. It's just one more unnecessary step that I don't want to take.
If you're building a Ruby on Rails application that requires authentication, you're probably going to want to use the Login Engine plugin. There's no point in rewriting something that's been written thousands of times before. The problem is that by default you will have to log in again every time you close your browser window. You don't want to modify the actual engine code because then you're going to have upgrade problems when they release a new version. I'll explain how I solved this problem.
The first thing you will want to do is create your own user.rb in /app/models/. Your code should look like this:
-
require 'digest/sha1'
-
class User <ActiveRecord::Base
-
include LoginEngine::AuthenticatedUser
-
-
def generate_login_token
-
Digest::SHA1.hexdigest("#{self.id}-#{self.salt}-#{self.salted_password}-#{Time.now.to_i}")[0..39]
-
end
-
end
This generate_login_token method will generate a unique login_token that will be stored in a cookie. We don't have a login_token field in our users table yet though, so let's add that now.
To add the login_token column create a new migration via the command line, "ruby script/generate migration AddLoginToken". Your AddLoginToken migration class should look like this:
-
class AddLoginToken <ActiveRecord::Migration
-
def self.up
-
add_column(:users, :login_token, :string, {:limit => 40})
-
end
-
-
def self.down
-
remove_column(:users, :login_token)
-
end
-
end
Run your migration and upgrade your database.
The next thing you will want to do is create your own user_controller.rb in /apps/controllers/ if you have not yet. Since the login engine has already implemented all the login* functionality in it's own user_controller.rb, what we want to do is add filter methods that will be executed after login and logout that will set the cookie, and delete the cookie.
-
after_filter :set_login_cookie, :only => [:login]
-
after_filter :delete_login_cookie, :only => [:logout]
-
-
protected
-
def set_login_cookie
-
if user?
-
login_token = current_user.generate_login_token
-
current_user.update_attribute(:login_token, login_token)
-
cookies[:login] = { :value => login_token, :expires => Time.now.next_year}
-
end
-
end
-
-
def delete_login_cookie
-
cookies.delete :login
-
end
The final step is adding a block of code that will check on every request if a user is logged in. If no one is logged in, it checks if the cookie exists. If the cookie exists, it will load the user from the database and stuff it into session. Edit your application.rb and add the following code:
-
prepend_before_filter :refresh_user_from_cookie
-
-
protected
-
def refresh_user_from_cookie
-
return if user?
-
if !cookies[:login].nil?
-
user = User.find_by_login_token(cookies[:login])
-
if !user.nil?
-
session[:user] = user
-
else
-
logger.warning "user not found by login token. #{cookies[:login]}"
-
end
-
end
-
end
That's all there is to it.












Awesome entry man…
I was looking for this stuff since morning or something.
Nice job. It works great! FYI, your 2 calls to after_filter have the wrong names (set_cookie vs. set_login_cookie)
I modified my version to check if the user checked a “Remember Me?” checkbox before setting the cookie.
thanks for posting this.
Oh, 1 more thing if you’re using login engine. I found that the engine doesn’t clear out the password and confirmation (after someone does a change password) so if you happen to be printing debug info, you just might see it. the “falsify_password” method of loginengine doesn’t do enough to clear it.
i added the following to user.rb:
after_save :clear_password_fields
protected
def clear_password_fields
self.password = nil
self.password_confirmation = nil
true
end
Thanks for the code.
I got this error though:
NoMethodError (undefined method `warning’ for #):
/app/controllers/application.rb:109:in `refresh_user_from_cookie’
My guess was I got this error because I was still logged in through a different computer. For now I removed the warning code from refresh_user_from_cookie to prevent it from breaking my code.
Is the a way to allow persistent login from different machines though?
I guess I was wrong. Your code works even when you login from multiple browsers.
I’m trying to adapt this code for a set of apps single-sign-on. One database, multiple rails apps.
I’ve tried changing the login cookie to use the domain instead of the app+domain as follows:
cookies[:login] = { :value => login_token, :domain => ‘.mysite.com’, :expires => Time.now.next_year}
The cookie write appears to work, but reading back cookies[:login] does not. I’m not sure if this is a problem with my code or this code.
thoughts anyone?