I’ve been working on a post about using JWTs JSON Web Token (JWT) when you can’t use HTTP cookies for sessions. As I dug into it, I came to realize that understanding the risks of using JWTs requires a bit of understanding about how HTTP cookies (and session) work. So, here’s a primer.
You probably have at least a passing familiarity with both cookies and session, but it’s worth a deeper understanding so you can make good choices when not using them. This time we’ll look at the nuts and bolts of cookies, next time sessions.
As you probably know an
HTTP cookie is a key
value store. Under the hood, the key/value pair is sent as an HTTP
Set-Cookie. When a browser sees this header it stores the
cookie. Next time the browser makes a request to the server, it sends
the cookie back in the
Cookie header. When setting cookies, multiple
cookies == multiple Set-Cookie headers. The browser, on the other
hand, can send multiple semicolon separate cookies in the same
If you met them on the street, they’d look something like:
All web frameworks, and even web friendly programming languages,
provide some structure so you don’t have to generate and parse cookie
headers yourself. In Rails, it’s a magical hash-like object called,
wait for it,
cookies! What can we do with it? A fine example is
saving a visitor’s language preference.
We set it:
and, as the visitor continues to browse the site, we can get it back:
By default, cookies are scoped to the host name, so a cookie from www.example.org would not be available to shop.example.org. (Cookies are never available cross domain, so foo.org is never going to see cookies from example.org.) However, the scope can be expand to the domain, i.e. .example.org, in which case cookies set by www are available to shop and vice versa. With this, we can set a visitor’s language preference across all of our sites:
In Rails you set cookie options by passing in a hash instead of just a value:
Cookies can also by scoped by path. By default cookies are available to any URL on the site, but it’s possible to limit them to a path, like /shop. Typically, this is used to avoid collisions when different apps use the same name for cookies. Again in Rails:
Cookies can and will expire. The default is when the “session ends” which typically means when you quit the browser. However, you can also set an explicit expiration. Say we need to display a disclaimer every 30 days:
Rails also has a shortcut for “permanent” cookie, which creates a cookie that expires in 20 years.
Keep in mind nothing is truly “permanent”, least of all data. The visitor may delete the cookies at anytime. They may also switch browsers or visit from a different device, neither of which will have the cookie set.
Cookies have two very important security settings, Secure and HttpOnly. Both of these are off by default in Rails.
Secure says “Only send this cookie over HTTPS.” When this is off, the browser will send the cookie every time it visits the site, when it’s on, the browser will only send the cookie when it visits HTTPS URLs, thus protecting the cookie from being sniffed on the network.
Note that this setting if for the browser only! It’s up to you to make sure the server doesn’t send “secure” cookies over HTTP.
HttpOnly is confusing as it doesn’t have anything to do with HTTPS vs
can access the cookie via
document.cookie, when it’s on, the cookie
Cross-site scripting (XSS)
Finally, rails provides two extensions to prevent tampering with or even viewing cookies values. Cookies are opaque to your average user, but with a bit of knowledge your can change them browser side. Consider a logged in user, you might store their user ID in a cookie and use it to display the correct dashboard. If the user changed that value, they would be able to view other users’ dashboards. Rails prevents this with signed cookies:
Read them back with:
An exception will be raised if the cookie has been tampered with.
If you want to both protect the cookie and hide it’s value, you can encrypt it instead:
Again, an exception will be raised if the cookie has been tampered with and thus can’t be decrypted.
If you get one thing out of this article it should be this, while cookies are handy for keeping a bit of state information, but once you need security, you have to pay attention do a bit of work. By default your cookies and the data they contain are a) public and b) not trustworthy.
It would be nice if there a tool to help make security little more foolproof. And of course, that’s our topic next time, Sessions!