Sessions and cookies in Ruby on rails

There are many things that we, as developers, rely on without giving it too much thoughts. They are taken care of by libraries that we use. A few examples? Routing, sessions, SQL processing, etc.

There's a reason why it's abstracted for you, it's boilerplate code that is easy to break and hard to get right. So while it's good to use mechanism that are vetted by the open source community, I believe it's also important to understand how the library you uses implements those structures as it can help you understand security tradeoffs that might not be obvious otherwise.

Another advantage of understanding the framework's underlyings is that it might gives you solutions when problems will occur.

One thing that is probably used by most of us and taken for granted is cookies handling in rails. Cookies can be used to set arbitrary data but this isn't the reason I want to talk about it today, I want to talk about cookies today because they are used to keep the session alive between requests and this is something that I believe, people have used without giving it too much thoughts.

Do you know how cookies are handled in Rails? If you don't, you're in for a few surprises!

Most of the things I am going to show here can be applied to rails 3 and 4 as the session handling in rails hasn't evolved much since rails 3.

Cookies + Sessions = ♥

Rails uses a CookieStore to handle sessions. What it means is that all the informations needed to identify a user's session is sent to the client and nothing is stored on the server. When a user sends a request, the session's cookie is processed and validated so rails, warden, devise, etc. can figure out who you are and instantiate the correct user from the database.

Like I said earlier, cookies are how server can remember who you are from one request to another. Everytime you send a request to a server, you send every cookie you have for that domain. Nothing new here. A session cookie is signed and encrypted (encryption is new in Rails 4) then sent to the browser. That cookie is actually an hash that would look like this if you run something like Warden and Devise:


cookie = {
  "session_id": "Value",
  "_csrf_token": "token",
  "user_id": "1"
}

Encryption & Signature

From the hash above, the cookie will become an encrypted and signed string that will then be sent to the client's browser in a Base64 format. Encryption is done with ActiveSupport::MessageEncryptor and the signature is done with ActiveSupport::MessageVerifier.

Those are the two gatekeepers for your application, if they are compromised, someone could log in as anyone. This is why Ruby on rails' guide specifies to use strong secrets for your session keys.

Encryption

You are probably used to key-derivation encryption (bcrypt, pbkdf2, etc). While these are useful for passwords, they are useless here because key-derivation functions cannot be decrypted (that's on purpose). Cookies, on the other hand, need to be decrypted when they are received to extract the content and assign the user to the request. So instead of a key-derivation function, rails uses a cipher (by default aes-256-cbc) to encrypt and decrypt the content.

Signature

Signature is appended to the serialized content of the cookie. First, the hash above is converted to a string, then rails append "--" to the end of the string and then append a signature that makes that the content of the cookie was not altered. Basically, what it does is it takes a secret token and make an hexadecimal digest of the token and the cookie (as a string). It's important to note that signing a cookie is merely appending an hexadecimal string to the end of the cookie's value. A user can read the data off your cookie if your cookie is not encrypted (they aren't prior to rails 4!).

Session id are not stored nor validated on the server side

I said earlier that everything was sent to the client and nothing was kept. By default, rails does not store the session id on the server. So, when the request is received, the cookies is decrypted and the signature is verified but the session id's verification is only made to make sure it exists. As far as rails is concerned the session id can be anything. Heck, every user could have the same session id and it wouldn't change a thing.

There's more than one storage type

By default, rails uses the cookie storage and it works fine for most of us. But if you want to store the session id, you have alternatives that works just as good. To change session storage, you need to change it in the configuration.


# config/environments/{production|development|test}.rb
YourApp::Application.configure do
  config.session_store = ActionDispatch::Sessions::CacheStore
end

The three storage that are available to you:

It's important to note that the last two stores will save the session id to the cookie unencrypted and unsigned.

Your own session store

If, for some reason, you need to customize a session store, I suggest you build your own by subclassing the current implementation. It will help you add your features while making sure that the underlying security is kept intact.


# lib/sessions/my_own_store.rb
require "action_dispatch/middleware/session/cookie_store"
module Rails
 module Sessions
   class MyOwnStore < ActionDispatch::Session::CookieStore
     #My implementations
   end
 end
end

Do remember that if you do customize the session store, it will be called for every request that goes through rails so it needs to be lightweight or you will slow down your whole application!

Have fun hacking!