Update: new syntax
cookies[:thing] = {:value => {:normal => "session stuff"}, :expires => 2.hours.from_now}
# Signed cookies are encrypted with the apps secret_token.
cookies.signed[:login] = {:value => @user.id, :expires => 1.day.from_now}
# These calls are equivalent.
cookies[:foo] = {:value => "bar", :expires => 20.years.from_now}
cookies.permanent[:foo] = "bar"
Following is the outdated and obsolete original version of this post.
I created a plugin for remember me checkboxes in Rails 3. This post explains how the plugin works.
Remember me
Dynamic session expiration is a fancy name for “Remember me” checkboxes on login forms. When unchecked, the session resets when the browser is closed. When checked, the session will last for a given time period (30 minutes, 2 weeks, 1 year, whatever), and when the user visits your site, the countdown resets.
This is how you set coookie expiration time in Rails 3:
env["rack.session.options"][:expire_after] = 10.minutes
This alone won’t do, though.
Cookie limitations
Setting :expire_after
will set the expires
attribute on the cookie. This causes the cookie to persist to the given date even when the browser is closed, while a normal cookie without expires
will last as long as the browser is open.
Here’s our problem: When we change something in the session
of our Rails app, the cookie needs to be updated. This is done by creating a entirely new cookie to replace the old one. The reason this is a problem is, as I learned recently, that browsers will only send the raw cookie data to our Rails app, leaving out the meta data such as the expires
of the cookie.
In other words, when we change something in our session
, we lose track of when the cookie was supposed to expire. There is no way for Rails to know what the original expires
was, since the browser doesn’t send that information to our Rails app.
Workaround
The simple answer is to use my plugin. Here’s what my plugin does, under the hood.
When we log in, we set the expiration time in our session.
def create
session[:remember_for] = 1.week
# Perform regular login..
end
In our application controller, we create an after filter that will pass this session variable to our new cookie.
after_filter :persist_session
def persist_session
if session[:remember_for]
env["rack.session.options"][:expire_after] = session[:remember_for]
end
end
When we want to log out, just have to delete the session key, and the session will return to normal.
def destroy
session.delete(:remember_for)
end
The after filter in the application controller will run before Rails creates the new cookie. This means that out :remember_for
from the old cookie will get passed on to the new cookie before it is replaced.
Win!
Full example, using the plugin
class SessionsController < ApplicationController
def create
session_expires_after = 1.day if params[:remember_me]
user = User.authenticate(params[:username], params[:password])
if user
session[:user_id] = user.id
# redirect_to ...
else
# ...
end
end
def destroy
session_expires_now # unsets the session key
session.delete(:user_id)
# redirect_to ...
end
end