http://www.thewojogroup.com/2008/09/remember-mes-with-rails/
Remember Me’s with Rails
I recently had a need for a login system that needed a ‘remember me’ function. After hours of looking through countless blogs, I came to the conclusion that either (1) people don’t use a remember me function with custom authentication systems for Rails, or (2) they don’t talk about it. In this article, I outline a simple remember me system using the cookie variable in Rails that will tack on to most custom authentication systems.
Using the cookie functions in Rails is pretty straightforward. It’s used with the ActionController and is quite simple to use. Most people use sessions for authentication, which is a good idea. Sessions, unlike cookies, automatically save your content as encrypted strings using the browsers cookies. ActionController#cookie provides a method for saving information in the browser, but you need to hash the content yourself if need be.
However, if a user selects the remember me option when logging in, we would like to have the session expiration set to be a longer period, like 30 days. Unfortunately, this is quite difficult to do if you don’t want to change the expiration of ALL sessions.
My site already uses sessions for authentication, and I’m going to leave that be. In fact, I’m not going to change anything about the session variable at all. This way, I can add this remember function to almost any authentication system I use in the future very easily.
When a user is authenticated and has selected the “remember me” option, I do two things:
- create a cookie that stores (plain text) the user’s id (you can use name, email, etc. but I prefer the id because it says nothing about the user to anyone trying to get information)
- create a second cookie with an hashed string of some other information about the user( name, email, address )
- if params[:rememberMe]
- userId = (@user.id).to_s
- cookies[:remember_me_id] = { :value => userId, :expires => 30.days.from_now }
- userCode = Digest::SHA1.hexdigest( @user.email )[4,18]
- cookies[:remember_me_code] = { :value => userCode, :expires => 30.days.from_now }
- end
For the hashing of the second piece of information, use a hash such as SHA1 or MD5. We can use these two cookies to authenticate a user when they return after a session has expired.
- if ( cookies[:remember_me] and cookies[:remember_me] and User.find( cookies[:remember_me]) and Digest::SHA1.hexdigest( User.find( cookies[:remember_me] ).email )[4,18] == cookies[:remember_me_code] )
- @u = User.find( cookies[:remember_me_id] )
- session['user'] = @u.id
- end
Just work that into your :before_filter for your authentication system, and you’re all set. Make sure you delete the variables when someone logs out:
- if cookies[:remember_me_id] then cookies.delete :remember_me_id end
- if cookies[:remember_me_code] then cookies.delete :remember_me_code end
****Edit: make sure to have “require ‘digest/sha1′” at the top of any page where you are using the SHA1 hash.
Tags: authentication, expiration, login, Programming, rails, remember me, RoR, Ruby, session
Posted in Programming 14 Comments »
restful_authentication has a remember me functionality. im pretty sure most of the auth generators and plugins to actually.
here are two links the explicitly mention “remember me” and how to activate it.
http://railsonedge.blogspot.com/2008/03/rails-forum-restful-authenticationpart.html
http://crazyrails.com/how-to-install-restful-authentication/
of course, if you were wanting to implement it in to your own system that’s a different matter, but you cant say that people dont use it when on of the most used authentication options for rails has it.
The restful_authentication plugin has “Remember Me” functionality built in. You just have to uncomment a line or two of code.
Thanks for letting me know, Mike. I thought that the restful_authentication might have that ability. However, I was tentative to use the plugin for this particular site, mostly because I have multiple login systems for one rails app. I will check it out in a bit more detail to see if I could use it in this context, but I did want to create a remember me system to use for custom authentication, as I have used them for a couple different sites.
Hey Steve, thanks for the comment. I corrected my wording. I was misleading in my intro paragraph, I was speaking about custom authentication systems.
I too am going to need multiple sites to all have the same login. But I’ve got a theory about an easy way to pull that off that I’ve been considering. If each site shared the same secret value which is used when encrypting the session information which is sent to the user’s browser and all the sites were off of the same domain (i.e. site1.domain.com and site2.domain.com) then every site could log them in and then every site would perceive the user as logged in when they came in because the user ID would already be in the session.
Worth a try?
Not a bad idea. However, it would depend on what user data you store in your session variables. As long as each site has similar user info in their database, or you are using the same database for all the sites, that would work fine (i.e. using email in the session would be easier than using user id if you used a different dB to store user data).
For this site, what I meant for multiple logins is that I have a user system for admins (i.e. viewing stats and creating posts) and a completely different, temporary system for beta users (for the beta launch, visitors must create a username to view the site, in order to limit traffic). So I have two different types of logins and user models, hence, two separate custom authentication systems, which is why I chose not to use a plugin.
But you have a good idea for using one session for multiple sites under one domain, as far as I know it should work.
Nice how-to! One thing though: Could it be that “cookies.delete :backbone_id” needs to be “cookies.delete :remember_me_id” instead?
Dear,
I am trying to implement this remember me code. In this you are checking the conditions called cookies[:remember_me]. May i know from where you are getting the code cookies[:remember_me].In the cookies we are not storing cookies[:remember_me] but when are checking in the before filter we are using this,so pls let me know reg this issues.
# if ( cookies[:remember_me] and cookies[:remember_me] and User.find( cookies[:remember_me]) and Digest::SHA1.hexdigest( User.find( cookies[:remember_me] ).email )[4,18] == cookies[:remember_me_code] )
Hi Brett,
I’m not convinced that your implementation is secure enough. If I know the user id and the email of another user, I can forge cookies to let me in. To make that more secure, I would add a secret string to the input of the SHA1 hashing function.
@xpmatteo, the idea was that by taking a random excerpt of the hash, it would be impossible to figure out. This is mostly because the attacker wouldn’t know what I used to create this hash.
–
However, I have become more prone to using a more complicated input to the hash since I published this, using multiple user fields along with a secret string to improve security.
–
Thanks for the feedback!
SHA1 is a hashing algorithm.
It’s not encryption. You can reverse encryption. You can’t reverse a (properly designed) hash.
The data for generating the remember_me_code should really be salted with some (random) value that changes on each login (and preferably on each usage of the value in the cookie). Otherwise it’ll be much easier to forge the remember_me_code cookie for an attacker.
The simplest method would be to add a logged_in_at attribute on the User model that gets updated after authentication and use that to salt the email before passing it to the hash function.
Excellent info, thanks!
Your second code block is full of typos. cookies[:remember_me] is never set. It should be cookies[:remember_me_id] and cookies[:remember_me_code] depending on the context.
Regardless, you pointed me in the general direction, and I was able to figure out the rest. Thanks for saving me several more hours of work.