Signed and Permanent cookies in Rails 3 7

Posted by pratik
on Friday, February 05

David added a very cool feature to Rails recently – Signed cookies and permanent cookies This lets you set permanent and/or signed cookies very easily.

Before this, you’d have to write :

1
2
3
4
cookies[:user_preference] = {
  :value => @current_user.preferences,
  :expires => 20.years.from_now.utc
}

Now just becomes :


cookies.permanent[:user_preference] = @current_user.preferences

In case you happen to have seen my Railssummit presentation I had talked about using ActiveSupport::MessageVerifier for implementing “Remember me” functionality. The above commit makes that a whole lot easier.

In your model User.rb :

1
2
3
4
5
# User.rb
def self.authenticated_with_token(id, stored_salt)
  u = find_by_id(user_id)
  u && u.salt == stored_salt ? u : nil
end

And when the user checks “Remember me” box, make sure the following gets run :


cookies.permanent.signed[:remember_me] = [current_user.id, current_user.salt]

This will set a permanent and signed cookie using the secret specified in ActionController::Base.cookie_verifier_secret. If you don’t have the cookie_verifier_secret defined, you might want to do that in one of the initializers.

Now when you want to login using the cookie :


user = User.authenticated_with_token(*cookies.signed[:remember_me])

In this specific case, it’s very important to use the salt in the cookie value. That makes sure the cookie gets invalidated if the user changes his password.

Comments

Leave a response

  1. AmrFebruary 05, 2010 @ 08:10 PM

    Much Easier! Thanks Pratik.

  2. Tinu CleatusFebruary 08, 2010 @ 04:41 PM

    Will come handy. Thank you!

  3. Gustavo BeathyateFebruary 09, 2010 @ 10:27 PM

    Shouldn’t it be like this:

    def self.authenticated_with_token(user_id, stored_salt) u = find_by_id(user_id)

    or like this:

    def self.authenticated_with_token(id, stored_salt) u = find_by_id(id)

    If not, I think I’m missing something…

  4. Lars PindFebruary 10, 2010 @ 12:09 AM

    @Gustavo: You’re right.

    //Lars

  5. John AdamsFebruary 10, 2010 @ 02:41 AM

    This solution is still horrible.

    Authentication tokens should NEVER be stored in the cookie. If the cookie is stolen and replayed, it becomes a password equivalent token. If the user clicks logout, and the cookie is stolen, the user is not logged out and the attacker can replay the token.

    The cookie should only ever hold a reference (a nonce) to a stored (in memcache or in the db) session.

    This has been a fundamental flaw with CookieStore from day one.

  6. GroxxFebruary 10, 2010 @ 04:32 PM

    @John Adams: that’s what I’ve been thinking too, though I wasn’t certain enough to comment on that. And, personally, I always use the database store for cookies for similar reasons.

    On another point, though, how is this a signed cookie? It’s just adding a chunk of info that the user (and other users) wouldn’t know, so you can’t just change the user_id to log in as someone else. If someone stole a bunch of cookies, however, they’d be able to log in as any of them forever, until the salt / user_id / validation scheme is changed, which is as bad as any other replayable option.

    If you’re going to claim to sign a cookie, sign the cookie with a digest of some kind so any value can’t be changed without invalidating it. Any you specifically want the user to be able to modify, you just exclude from the digest calculation. And be prepared for someone to find and reverse your hashing scheme, because it’s still possible, and you’ll still need server-side checking to make sure it’s not a security hole.

  7. etdFebruary 25, 2010 @ 03:06 PM

    @Groxx, cookies in Rails are signed using an HMAC with SHA1:

    http://github.com/rails/rails/blob/47498a7f59d0196e9b8aa8e3569cbb4937477cef/activesupport/lib/active_support/message_verifier.rb

    @patrik, I would recommend expiring refreshing the ‘remember me’ token after it is used to avoid replay scenarios.

Comment