In Part 1 (When the Cookie Crumbles) we saw what issues can happen when using Session ID Cookie and storing the session state on the server. In this post, we will learn about client-side sessions, and how they can solve those problems.
Keeping It Local
So now that we have seen the issues keeping the session on the server, what is the solution?
Simple – keep it on the client, inside the user browser. By storing the session state in the browser, and sending it back with every request to the server, we can avoid all of the major disadvantages of server-side sessions:
- We don’t need to worry about server restarts or application recycles: since the session state is not stored on the server we don’t have to worry about losing session state when it’s recycled or keeping client and server sessions in sync.
- Session state can be used by different applications: When storing session state inside the process memory, only a single application can use it. But when it comes inside the HTTP request, different applications can use it, making it easy to create hybrid sites which are made from different services and technologies.
- Easily support multiple server clusters: No need for sticky session, shared database, or distributed cache. No need to rely on 3rd party products, use a complicated framework or implement custom solutions for session management. This avoids many synchronization and performance issues and allows scaling up or down according to site traffic (ideal for cloud environments).
- Better server utilization: Storing users session in memory can be problematic if the site has a lot of active users or large session state objects. Not storing user session in memory can free up a lot of the server memory, which can be used to handle more users instead. Also not having to sync all of that data between servers or go and fetch it from the database for each request, can free a lot of CPU time and reduce network “chatter”. In short, each server will be able to handle more traffic.
- No dependence on other applications or people: All of the options of storing server side in a multi-server environment have one weakness in common: they all rely on other applications and people in order to work. For example: F5 or Imperva for sticky session, Memcached or Redis for distributed cache, and SQL or NoSQL databases, are usually not operated by web developers. This means that in both the initial configuration and day-to-day maintenance IT support is required in order to keep the site going. In other words – the site scalability comes from IT, and not from the site itself.
The site scalability comes from IT, and not from the site itself
- No single point of failure: Besides being depended on others, all of those options are also a single point of failure. This means that if they stop working properly, the entire site goes down and we depend on other people in order to bring it up.
The end result is that if we manage to avoid server-based session, we can greatly lower the site complexity while increasing its stability and scalability – great news for both the site visitors and the site owners.
However, there are some issues when storing user session in the client, and the first problem is data security. After all, if we keep the session state in the client, what prevents malicious user changing its session state before sending the request? They would be able to change their balance or impersonate another user, which is a clearly a concern that must be addressed.
Another issue is where do we store the user data? We have seen before cookies are not a good option because they do not support mobile & desktop applications and can’t be used with other sites. We need something else.
And this is where Tokens and Claims-based authentication comes into play.
Claims To The Rescue
So the first thing we need is a method to store the critical properties of the logged in user (username, email, name etc) inside the browser in a secure way. This is where the idea of “claims” come in.
Claims & Tokens
Claims are assertion we make about the user. They can be the user ID, email, name and everything else we would like to know about the user.
When a user signs into the site, we validate them and generate a list of claims about them, based on their user profile fields.
This list of claims is placed inside a container called the token. We then return that token to the user. From that point onward, the token is included in any further requests they make to the server. The server extracts the list of claims from the token and uses them in order to identify the user who is making the request. No lookup is required (to external database, applications or memory) since instead of a single session identifier, the server receives all the information it requires in the request itself.
This concept of storing the user identity as claims is called Claims-based identity, and it allows us to use the browser as the “glue” that connects the user session together, without the need to use server-side session.
Of course, this solution will not be secured if the user was able to change the claims before they are sent to the server. This is why all the claims implementation protocols (like SAML and JWT) comes built-in with a signing method which uses cryptography in order to make sure that the token was not tampered with.
Read more about claims-based authentication
– What is Claims-Based Authentication?
– A Guide to Claims-Based Identity and Access Control
JWT (JSON Web Tokens)
JWT (JSON Web Tokens) is defined as a part of the OAuth spec which talks about how to store and transfer claims between sites. They are used to replace SAML tokens because they are much smaller (more on that subject in the next part).
JWT are encoded in Base64Url. This means that they can be passed in URL, POST parameter, or inside HTTP headers.
As a result of their format and small size, JWT tokens have the following advantages:
1) They can be used in order to authenticate and authorize all requests, especially when working against REST services. In other words, they can be used instead of cookies in order to manage user sessions.
2) They can be easily used in native applications, desktop applications and server-to-server communications for SSO needs – where cookies are harder to use.
3) They are easy for humans to understand. For example, the site jwt.io allows us to easily see (and modify) the content of JWT tokens.
4) They offer better security than normal cookie because they are signed and have a built-in expiry date (and start date).
Since they are a part of OAuth and OpenId protocols, JSON Web Tokens are being used by many companies in the world in order to support social login and resource sharing. And as a result of their advantages, they are also being used for other purposes such as storing client-side sessions and to create “magic links” for password-less authentication or email verification.
For more information about JWT format visit:
Managing Session With Tokens
As we can see, JWT tokens are a great place to store our client-side session: instead of returning
Session ID Cookie, we can create a JWT token with all the information required about the user (including their roles). Then we only need to make sure to send that token with every request in order to know who is making the request, saving us the need to store user sessions on the server.
But there are other aspects of session management which needs to be addressed before we can fully use tokens to replace server-side sessions, and getting them right is not always easy.
User sessions can’t live forever – at some point, they have to expire. This is mainly for security reason – for example, when a user walks away from his computer we don’t want other people who may be using the same computer to continue using his session. Another concern is session hijacking – this is when an attacker manages to compromise a user browser and steal their session cookie or token, and gain full access to the site impersonating their victim. Because of this, all sessions have to have a limit to the time they can be used (usually between 30-60 minutes).
Before we talk about token duration, let’s see how this is implemented with cookies.
When the server creates a cookie and sends it to the browser, the server can also specify when does that cookie expire. The browser will then keep that cookie until the expiry date arrives – which will cause the browser to delete the cookie.
The good thing about this technique is that the browser automatically deletes any expired cookie, which means that in normal situations the server will never get an expired cookie. The less good thing about this technique is that the cookie expiry date is kept as a separate meta-data field about the cookie, which means everyone can change that date (from the developer console for example) and the cookie will be sent even if it’s after it’s expiry date.
Since the server can’t rely on the cookie expiry date, cookie-based session management systems have to keep track on when sessions were created (on the server side) in order to be able to know when they expired.
Now let’s see how tokens expiry works. When issuing a token, one of the claims inside that token is the token expiration date. Since it’s inside the token, it is digitally signed – meaning it can’t be changed without breaking the token signature. Because of this mechanism, servers have no problem trusting the token expire claim date (of course, after validating the token). This means they don’t have to store anything on the server.
As a bonus security feature tokens also contain
not before claim which means – don’t respect this token before this date (usually the time it was issued). This also helps to prevent session hijacking and replay attacks.
Limiting session duration is only half of the equation – we also need to be able to extend it.
The reason for that is that there are times when users want to use the site for a period larger than the session length we limited our sessions.
In fact, in many popular sites, users don’t leave the site at all – they can spend days, weeks or even months connected to the site (for example Facebook or Gmail). If session were to expire every 30 minutes and they would have to log in again, it would make the use of those sites almost not practical.
For this reason, session management system has to implement a way to extend the session (in theory they could just increase session timeout to a couple of hours or even days but that will result in the site being very vulnerable to session hijacking and replay attacks which is what session timeout comes to prevent).
Let’s see how cookies handle session extension. A normal practice for cookies when dealing with session timeout is to use “sliding expiration” technique. The way it works is that for every request, the server looks at the session expire date, and issue a new cookie for an extended duration before it expires. Since cookies are automatically managed by the browser, when the browser receives a new cookie in the response, it will replace the existing cookie – resulting in an extended session.
The advantage of this method is that since it’s automatically handled by the browser, it can happen in any request to the server and the browser will extend the session without site developers needing to do anything. The disadvantage is that we require an active session to extend, in other words, if the session expired (for example the user has taken a long coffee break) we won’t be able to extend the session and the user would have to log in again to the site.
Now let’s see how session extension is done with tokens. With tokens, instead of sliding expiration we instead rely on a refresh token. Refresh tokens are opaque strings which are returned by the server after a successful login. They can be later used in order to get a new session token (which is called “access token” in OAuth terms).
Refresh tokens are meant for long-term use – they can last for weeks, months, years – or even to never expire. Because of that, they must be kept in a secure way in the client, which is why storing them inside the browser can be problematic. You can read more about them here.
A clear advantage of refresh token over cookie sliding expiration is that refresh tokens can be used to silently keep the user logged-in, without requiring them to re-enter their credentials (of-course until they expire). But unlike cookies sliding expiration, refreshing access tokens is not done automatically by the browser and require special client-side implementation.
Besides extending the user session, we also need a way to close an active session. This is required in order for users to be able to logout, but it is also a standard practice to terminate active sessions when a user password is changed, their role is changed, or they become disabled. Terminating session can also be required when detecting suspicious activity in a user account. In all of those cases, we need a way to close any existing session and force the user to log in again.
With server-side sessions closing sessions is easy: all we need to do is to delete the session associated with the user on the server side, and clear they session id cookie. But token based sessions only expire according to their
expire claim date. This means we have to wait until the token has expired which can compromise our system security.
This is why, when using tokens for the session, we need to implement a token revocation system. It can be based on:
- white-list: the recommended way by security experts, where we maintain a list of active JWT session tokens and only those in the list are allowed.
- black-list: the more practical way; the reverse of whitelist, where we maintain a list of revoked tokens.
- security stamp: which is a method of checking if any of the user critical information (password, email, roles, status) has changed since the token was issued.
The main problem with token revocation systems is that they all require server synchronization (by the means of shared memory or database), which could bring back some of the stability and performance issues we encountered before (with server-side sessions). However, this problem is much smaller because: first, we only need to maintain the state for blacklisted tokens and not all logged-in users; second, we only need to store the token identifier (
jti) instead of the full user profile; third, we don’t need to check expired tokens which makes the blacklist even smaller; and finally, in some systems we can trade-off some security by only checking for token revocation when getting a new access token, which means once every hour (or how long access token stay alive) instead of every request which is the case with server-side sessions.
Where the token is stored has great security consideration implications. In theory, access tokens should be only kept in a secured trusted place (this is especially true for refresh tokens, which rarely expire and allow full access to the site). For mobile apps, this could be SharedPreferences for Android or the Keychain for Apple devices. You can read more about this in this post. But what about browsers? Where can we store the session tokens?
There are 2 potential places for storing tokens in the browsers: inside the browser Web Storage or inside a Session Cookie.
Inside Browser Storage
We can use the browser web storage (local/session storage) for storing the session token in order to persist the session upon page reload (we didn’t have that problem when using cookie-based session because they stay until the browser is closed or even after that). But using web storage makes tokens vulnerable to XSS (Cross-Site Scripting) attacks – which will allow attackers to high jack user session and gain full access to the site (this can be the result of a mistake in the site code or malicious/compromised external scripts).
Because storing tokens inside the browser storage can be dangerous, the current recommendation is to only store short-lived, limited access tokens. Refresh tokens should be never kept inside web storage (local or session), which is why OAuth flows which are meant to be used by SPA don’t return them.
Note: Preventing session replay attacks is hard, even for cookies-based sessions. Shortening the session duration can help, but for added security, it is also better to consider using Content Security Policy or Browser Fingerprint. And always use HTTPS to secure the entire site (including all pages and resources).
- The Cookie crumbles, JSON Web Tokens to the rescue! – Ayan Dave
- Death to Cookies, Long Live JSON Web Tokens
There is another option for storing JWT tokens – inside a cookie.
You must be thinking – wait, isn’t that what we were trying to prevent? isn’t using cookies bring us back to the starting point?
But in fact, storing JWT session tokens inside a cookie can be a good option for our own site sessions. Because our site sessions are not shared with other sites, cookies are the best place to maintain access tokens. They will only be sent to our site domain, and when we use secured, HTTP-only, session cookie, we are not vulnerable to XSS attacks. And as a side bonus, we don’t need to implement clever client-side infrastructure to handle session management because it’s been automatically handled by the browser.
The only thing we need to be concerned about when using session cookies is to protect against CSRF attacks. Without CSRF protection, active sessions can be abused by attackers without the user’s knowledge. There are a couple of ways to prevent it, including the modern “same-site cookie”. You can read more about it here.
This is a good place to clarify a common misconception. Many people think that JWT is meant as a means to transfer identities between sites (SSO), usually for a social-login purpose. This is probably because they first encounter JWT when reading about OAuth and OpenID. While they are being used for that purpose in OAuth protocol, they can also be used for carrying session information (for authorization and authentication). In fact, JWT tokens are also used for that purpose inside the OAuth protocol. JWT is just a token format, which can be used for different purposes including storing our site session.
For more information regarding tokens-vs-cookies you can read the following topics:
- JSON Web Tokens vs. Session Cookies: In Practice
- Build Secure User Interfaces Using JSON Web Tokens (JWTs) – Robert Damphousse
One big challenge for token-based sessions can come from the effort required to implement them.
Since browsers don’t natively support token-based sessions, we require to implement all the session management components ourselves:
- handle Login/SSO flows
- persist the token
- submit the access token with every request inside
Authorization: Bearerheader (bearer authentication)
- get new access token before the session expired (silently or interactive)
- handle cases where token expire in mid-operation without losing track on user activity
The last requirement is especially difficult to implement – tokens can expire at the beginning or the middle of a series of REST calls, and handling that scenario without interrupting user operation can be very tricky. For this reason, it is recommended to use modern client-side frameworks which can handle that (like Angular or React), but even with those frameworks it is not an easy thing to do:
- Angular Security – Authentication With JSON Web Tokens (JWT): The Complete Guide
- React Simple Auth: React + Redux + OAuth 2.0
- Build a React Native App and Authenticate with OAuth 2.0
- Secure Your React and Redux App with JWT Authentication
Besides special client-side implementation, our server-side code also needs to be able handling:
- Authentication/SSO flows
- Issuing signed tokens
- making sure bearer tokens are valid for every request
- Token revocation
- and more…
Correct and secured implementation on the server side is also very important. Because we are talking about a very complex security-related system, we shouldn’t create such a mechanism our-self, like the saying:
Instead, owe should be using a well known, trusted server-side implementation. Many modern web framework comes with their own OAuth and JWT implementation, including the usage of JWT tokens inside session cookie (many of them also encrypt the cookie):
- Use cookie authentication without ASP.NET Core Identity
- Using JWT with Spring Security OAuth
- Authenticating a Session Cookie in Express Middleware with JsonWebToken
Using the session management solution which comes with our platform (for both bearer and cookie-based authentication), is the recommended way when dealing with JWT tokens. Implementing custom JWT tokens solutions (client-side or server-side) is a risk which should be avoided.
If we want to add a token-based session to our site but our platform is lacking proper support, another possibility is to use external identity providers (IdP). This means that users are stored in another site (like Facebook or Google), and all session management operation is done by that external site – freeing us from the need to implement most of those features in our site.
When using external identity provider, they often provide client-side library which we can use in order to implement such solutions. For example:
Using an external identity provider is a better option than writing our own custom token-based session implementation.
Challenges with token-based client session
Because getting token based session right can be very difficult to get right, many recommend to totally avoid using JWT tokens for session management:
- Stop Using JWT for Sessions
- Why JWTs Suck as Session Tokens (also read the comments)
- The Unspoken Vulnerability of JWTs
While all the points they make are worthy of consideration, and clearly there is no one single answer which fits every scenario, allow me to make the following counterpoints:
- some article warns about using JWT for sessions, but they actually mean “don’t use JWT token stored inside browser storage” for sessions. JWT is just a format, and using JWT tokens inside cookies can bring us to the same level of security as cookies usually have.
- using JWT tokens inside browser storage is the only place SPA applications can use. Also, many sites now use separate domains for storing site assets (HTML/JS/CSS/Images) and site data, even if they are not full SPA – for example serverless sites which work directly against database API (for example Firebase) or serverless code (Amazon Lambda, Azure Functions) and other APIS. In all of those scenarios, cookies can’t be used because site domain is different from where it’s data sits.
- using tokens is mandatory when consuming REST services from other domains. All big companies in the world (Microsoft, Google, Amazon etc) use token-based sessions to allow accessing their services from other domains (or servers).
- some warn for not using JWT for sessions comes from how easy it is to get the implementation wrong. It is a fair warning, which is why we should use well known trusted solution and perform security review to verify the site is secured (which is a general recommendation anyway)
- Even inside cookies, JWT can be useful in order to reduce the amount of server-to-server communication required for authentication, authorization and in many cases they can be used to avoid fetching some (or all) of the user data from the database. Of course, this depends on the specific site requirements, but you can’t dismiss JWT entirely because they are not always suitable for all scenarios.
- It is true not every site needs scaling. However, using client-based session is the only way to avoid the single point of failure problem all server-based session implementation share, and greatly increase the site stability.
The bottom line:
It is important when designing the site architecture to consider the pros and cons of using JWT sessions. And in order to do we need to consider another important aspect of session management – where do we keep our session data.
Continue reading in Part 3 – Designing for scale.
Read more about JWT Angular implementation:
Ditching Cookies for JSON Web Tokens
JWT Authentication with AngularJS
Cookies vs Tokens. Getting auth right with Angular.JS