This is episode 3, out of a total 12, in the A Node.JS Holiday Season series from Mozilla’s Identity team. It covers using sessions for scalable Node.js applications.

Static websites are easy to scale. You can cache the heck out of them and you don’t have state to propagate between the various servers that deliver this content to end-users.

Unfortunately, most web applications need to carry some state in order to offer a personalized experience to users. If users can log into your site, then you need to keep sessions for them. The typical way that this is done is by setting a cookie with a random session identifier and storing session details on the server under this identifier.

Scaling a stateful service

Now, if you want to scale that service, you essentially have three options:

replicate that session data across all of the web servers,

use a central store that each web server connects to, or

ensure that a given user always hits the same web server

These all have downsides:

Replication has a performance cost and increases complexity.

A central store will limit scaling and increase latency.

Confining users to a specific server leads to problems when that
server needs to come down.

However, if you flip the problem around, you can find a fourth option: storing the session data on the client.

Client-side sessions

Pushing the session data to the browser has some obvious advantages:

the data is always available, regardless of which machine is serving a user

there is no state to manage on servers

nothing needs to be replicated between the web servers

new web servers can be added instantly

There is one key problem though: you cannot trust the client not to tamper with the session data.

For example, if you store the user ID for the user’s account in a cookie, it would be easy for that user to change that ID and then gain access to someone else’s account.

While this sounds like a deal breaker, there is a clever solution to work around this trust problem: store the session data in a tamper-proof package. That way, there is no need to trust that the user hasn’t modified the session data. It can be verified by the server.

What that means in practice is that you encrypt and sign the cookie using a server key to keep users from reading or modifying the session data. This is what client-sessions does.

Immediate revocation of Persona sessions

One of the main downsides of client-side sessions as compared to server-side ones is that the server no longer has the ability to destroy sessions.

Using a server-side scheme, it’s enough to delete the session data that’s stored on the server because any cookies that remain on clients will now point to a non-existent session. With a client-side scheme though, the session data is not on the server, so the server cannot be sure that it has been deleted on every client. In other words, we can’t easily synchronize the server state (user logged out) with the state that’s stored on the client (user logged in).

To compensate for this limitation, client-sessions adds an expiry to the cookies. Before unpacking the session data stored in the encrypted cookie, the server will check that it hasn’t expired. If it has, it will simply refuse to honour it and consider the user as logged out.

While the expiry scheme works fine in most applications (especially when it’s set to a relatively low value), in the case of Persona, we needed a way for users to immediately revoke their sessions as soon as they learn that they password has been compromised.

Every API call that looks at the cookie now also reads the current token value from the database and compares it with the token from the cookie. Unless they are the same, an error is returned and the user is logged out.

The downside of this solution, of course, is the extra database read for each API call, but fortunately we already read from the user table in most of these calls, so the new token can be pulled in at the same time.

Technical Evangelist & Editor of Mozilla Hacks. Gives talks & blogs about HTML5, JavaScript & the Open Web. Robert is a strong believer in HTML5 and the Open Web and has been working since 1999 with Front End development for the web - in Sweden and in New York City.
He regularly also blogs at http://robertnyman.com and loves to travel and meet people.

I’ve just one question.. Couldn’t you just delete the server side key in order to destroy the session?
The user (or the attacker in the compromised account scenery) will simply notice it’s been disconnected at the next request.

I have seen this set up a number of times before in the old days and personally I don’t like it (at all). There are a number of security related reasons for that: First of all I don’t believe that encryption should be the primary security mechanism of any system. Why? Because encryption has a habit of failing as time passes. This probably won’t hold true forever, but it has been true for quite a number of times now. For example, I don’t know whether you’re using a separate encryption key for each user or a shared server key, but the first would be ‘hackable’ (and I have seen that being done in real life for an important application, though it was relatively harder there to collect a large number of encrypted pieces of data). The second should be alright (for now). And either way, this is purely theoretical, but e.g. quantum computers will have a huge impact on various encryption schemes, and yet, it’s unlikely that this specific application will exist by then, but what I am trying to say is that an application should be fundamentally secure. And letting the user store secure data is *not* the way to make an application fundamentally secure.

Oh and, besides that, I doubt that the overhead of a central server would be bigger than the overhead of sending all session data from the client to the server constantly (client upload is *slow* often).

The server secret is the same for every user. That’s why you need to pick a reasonably long and random string for it. If you do that, then an attacker won’t be able to brute-force your key in any feasible timeframe.

Of course, you can rotate the site secret at any point if you have reasons to believe it has been compromised. Doing so will invalidate all existing sessions.

Actually storing data on the client allows anyone to make massive brute force attack on the encrypted data.

As a consequence the admin needs :
– to setup a strong encryption (I would say 512 bits is a minimum)
– to track the progress of technology to always keep ahead.
– to hope there will be no sudden break through (Quantum CPU …) that destroy your encryption.

Client side sessions have two additional issues.
1. If you store too much data in a session, you exceed the maximum header size an HTTP request can have. Thus every request fails, and recovery means closing the browser for most users.
2. Clien side sessions are prone to race conditions. Request A is slow and while executibg request B comes by and modifies the session in it’s response. If A now finishes and also updates the sessions, it didnt see B’s change which is then lost.

Both of these issues are very real. We’ve hit #2 and it’s not fun to diagnose. You can mitigate this by path-scoping your cookies, striving for a one-page apps design, and being careful about needless session writes.

But if you have to scale – eliminating the need to synchronize session data across colos with low latency requirements is huge in how it simplifies scaling.

I’d say it would be hard to retrofit an existing application given these challenges – but we started very early in design with client side sessions, and given our scaling ambitions, for us it made good sense. YMMV!

Nice drawbacks highlighted by Erlend and Simon. There is a reason not so many sites implement client-side sessions…

About the race condition described by Erlend: wouldn’t most web apps suffer from the same problem anyway? I imagine that the process answering to req. A only reads session data upon its start (from db to ram), and when it tries to update it at its end, it stores it (to DB) without checking if there was any update in-between. Unless there was some incrementing-counter stored as well along with the session data, incremented w. every update, which could be checked

Not necessarily. It depends on how the session is handled. I ran into this problem with a rails app that was updating the session very often. When I moved the session from client-side to in-memory, everything worked as expected.
For an inmemory-store you might have other problems. One request may see an incosistent view of the session if another request is in the middle of updating it.
For database-stored session you sessions can be handled correctly by transactions, but that may impact performance. Usually though database-stored session will also be cached, and can be updated without locking. In this case they may suffer from the same problems as the inmemory-store.

In my opinion the probability of the race conditions due to multiple requests is greater than that of a dirty read.

I would really appreciate some detailed explaining or just some ramblings on viability of storing encrypted session on client side.

Very attractive idea, but are there any production use cases?
What encryption methods are available and optimal for this implementation?
What decryption methods are available for chosen encryption strategies?

I had to think about a similar scheme. Instead of a token string I’d use a plain timestamp. Store the creation date in the cookie (I had to anyway) and a date in the user table. When the user clicks “revoke sessions” the date in the user table is updated to “now”. Compare the timestamps to see if the server should accept the cookie.