We build Web & Mobile Applications.

< All Articles

Revisited: Tamper-proof cookies in Rails 3

Here’s a revisited post that’s fairly short and sweet: way back in 2008 I blogged about my implementation of tamper-proof cookies which used a similar technique to that used by Rails for its cookie-based session store. Back then the solution involved a custom cookie jar, the OpenSSL library to generate a HMAC, overriding the ApplicationController#cookies method and a slightly unorthodox method signature for reading cookie values.

Here’s a recap of the code in action:

# Write a tamper-proof cookie
cookies[:test_protected] = { :value => "protected", :protect_from_forgery => true }

# Read a tamper-proof cookie
cookies[:test_protected, true]
=> "protected"

# Read a raw cookie
# This returns the actual contents of the cookie, including the HMAC, and does not perform tamper checking
=> "protected--ecec30eed6e122f3a3d5bb914bdd3cc1da4bd28e"

And here’s the nicer Rails 3 version:

# Write a tamper-proof cookie
cookies.signed[:test_protected] = "protected"

# Read a tamper-proof cookie
=> "protected"

# Read the raw cookie (this gives you the encoded value and the HMAC)
=> "BAhJIg5wcm90ZWN0ZWQGOgZFRg==--18368dd442679a298bc98267d1d45c3046f636a7"

As you can see the magic happens when you use the signed method to read and write cookie values. There are two differences between my code and the Rails 3 version:


The only configuration option you might need to adjust is the secret token used when generating the HMAC. Rails will automatically generate a long, random string when you generate a new app that looks something like this:

# lives in config/initializers/secret_token.rb
MyApp::Application.config.secret_token = 'bdf998dad6dcc939bb3285131f2c902ec6d586ae973c02b05ad5ad2be47ade55054be99717a7c6e4191c3283fe6cedf7555126d9d360b996134f5978bc9520c8'

Normally you’ll not need to change this, but if you do make sure it is sufficiently long (minimum of 30 characters) and random enough so that it can’t be guessed easily, otherwise your tamper-proof cookies become somewhat less useful.

Rails 2.3.6 and newer can do it to

Despite the misleading title of this blog it’s actually not just Rails 3 that has this neat functionality: it first appeared in Rails 2.3.6 last May. Usage is exactly the same, but you will need to set the cookie_verifier_secret setting in an initializer like this:

ActionController::Base.cookie_verifier_secret = 'bdf998dad6dcc939bb3285131f2c902ec6d586ae973c02b05ad5ad2be47ade55054be99717a7c6e4191c3283fe6cedf7555126d9d360b996134f5978bc9520c8'

Remember to set your own secret (don’t just copy this one!), Rails has a handy rake task to help you:

rake secret
=> 54540ac4c22d48e24a88398d360b52465e2457a5d6bee99dc897cf5362098555b0940734a1a58b576c2138eea444be392412e81fb712d961a622b099e0cdae74

Rails 2.3.0 to 2.3.5 with ActiveSupport MessageVerifier

If you’re a fan of the classics you may still be using a version of Rails from 2.3.0 to 2.3.5. Under the hood newer Rails versions are using the ActiveSupport::MessageVerifier class to sign and verify cookies. The good news is that this class first made an appearance in Rails 2.3.0 so you can enjoy tamper-proof cookie goodness too. For example:

class ApplicationController < ActionController::Base

    def read_verified_cookie(name)
    rescue ActiveSupport::MessageVerifier::InvalidSignature

    def write_verified_cookie(name, value, options = {})
      cookies[name] = options.merge(:value => cookie_verifier.generate(value))

    def cookie_verifier
      # Note: the 'secret' string should really live in a configuration file
      @cookie_verifier ||= ActiveSupport::MessageVerifier.new("54540ac4c22d48e24a88398d360b52465e2457a5d6bee99dc897cf5362098555b0940734a1a58b576c2138eea444be392412e81fb712d961a622b099e0cdae74")

If you don’t mind the ActiveSupport dependency then you can also do a similar thing in a Sinatra application too.

And finally if your app is using a really old pre-2.3 version of Rails you might as well stick with my trusty old cookie jar from the original blog, although I’d recommend tweaking the code to remove the TamperedWithCookie exception and to Base64 encode the cookie values to make it easier if you migrate to Rails 3 in the future.

Updated on 07 February 2019
First published by Rob Anderton on 25 January 2011
© Rob Anderton 2019
"Revisited: Tamper-proof cookies in Rails 3" by Rob Anderton at TheWebFellas is licensed under a Creative Commons Attribution 4.0 International License.