9

I need to store a small piece of data (less than 10 characters) in a cookie in Rails and I need it to be secure. I don't want anybody being able to read that piece of data or injecting their own piece of data (as that would open up the app to many kinds of attacks). I think encrypting the contents of the cookie is the way to go (should I also sign it?). What is the best way to do it?

Right now I'm doing this, which looks secure, but many things looked secure to people that knew much more than I about security and then it was discovered it wasn't really secure.

I'm saving the secret in this way:

encryptor = ActiveSupport::MessageEncryptor.new(Example::Application.config.secret_token)
cookies[:secret] = {
  :value => encryptor.encrypt(secret),
  :domain => "example.com",
  :secure => !(Rails.env.test? || Rails.env.development?)
}

and then I'm reading it like this:

encryptor = ActiveSupport::MessageEncryptor.new(Example::Application.config.secret_token)
secret = encryptor.decrypt(cookies[:secret])

Is that secure? Any better ways of doing it?

Update: I know about Rails' session and how it is secure, both by signing the cookie and by optionally storing the contents of the session server side and I do use the session for what it is for. But my question here is about storing a cookie, a piece of information I do not want in the session but I still need it to be secure.

Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • Out of interest, why does it need to be stored in a cookie rather than server-side, where it would be by definition more secure? – Russell Feb 20 '12 at 16:56
  • @russell well, you need to store something in the browser, right? otherwise you wouldn't be able to identify it again. Whether it's the actual data or an id of a record on a table it's a matter of preference and necessity regarding the size of the data and other considerations. What I'm storing is already the id of a record in the table. I could create another table, but it seems like a waste in this case. – Pablo Fernandez Feb 20 '12 at 17:14
  • I've added an answer because I think changing the default session store to use the DB will give you the security you want with very little overhead with regards to creating tables etc. – Russell Feb 20 '12 at 23:35
  • @Russell I do not want to use the session. My sessions are per domain (blah.example.com) while this piece of data is for the whole system (.example.com), hence the domain option when setting it. – Pablo Fernandez Feb 21 '12 at 10:00

3 Answers3

13
  • Setting a secure cookie

    cookies.signed[:secret] = {
     :value => "foo bar",
     :domain => "example.com",
     :secure => !(Rails.env.test? || Rails.env.development?)
    }
    
  • Accessing the cookie

    cookies.signed[:secret] # returns "foo bar"
    

The cookie is signed using ActionController::Base.cookie_verifier_secret. You can set the cookie_verifier_secret in the initializer file.

Harish Shetty
  • 64,083
  • 21
  • 152
  • 198
1

As KandadaBoggu says, it looks like what you want is a session variable, and session variables are by default encrypted and stored in cookies. However, if you have a look at the contents of config/initializers/session_store.rb you will find something like the following:

 # Be sure to restart your server when you modify this file.
 MyRailsApp::Application.config.session_store :cookie_store, :key => '_my_rails_app_session'

 # Use the database for sessions instead of the cookie-based default,
 # which shouldn't be used to store highly confidential information
 # (create the session table with "rails generate session_migration")
 # MyRailsApp::Application.config.session_store :active_record_store

Which suggests to me that you should use the database for sessions instead of the cookie-based default, which shouldn't be used to store highly confidential information. The pre-cooked migration makes everything really easy to set up so there's very little overhead in doing so, and once it's done there's basically zero overhead if you need to add a new piece of secret information at a later date!

Russell
  • 12,261
  • 4
  • 52
  • 75
  • I do not want to use the session. My sessions are per domain (blah.example.com) while this piece of data is for the whole system (.example.com), hence the domain option when setting it. – Pablo Fernandez Feb 21 '12 at 10:01
1

I'm re-posting JacobM's answer, that he deleted, because it was the correct answer and pointed me in the right direction. If he undeletes it, I'll delete this one and pick his as the best answer.

First of all, if you use encrypt_and_verify instead of encrypt it will sign the cookie for you.

However, when it comes to security, I always prefer to rely on solutions that have been vetted in public, rather than rolling my own. An example would be the encrypted-cookies gem.

Community
  • 1
  • 1
Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • I curious why you would not use the signed cookies?(supported by default in Rails 3.x) – Harish Shetty Feb 22 '12 at 20:05
  • 1
    @KandadaBoggu The cookies processed by encrypt_and_verify or the encrypted-cookies gem (post 1.0) are signed, but also encrypted. It wouldn't be that serious if this piece of data gets exposed, but I rather not think about it and just keep it secure by being encrypted, thus, opaque to the user. – Pablo Fernandez Feb 23 '12 at 10:50