Adding membership support to sites has changed drastically over the last decade. Instead of basic authentication with username and password, modern sites are now required to support modern authentication protocols like OAuth, OpenID and occasionally even SAML.
Besides bringing new ways for sharing users identities between sites, these protocols also bring a new concept with them – the client-side session. This idea is in the heart of those protocols, and understanding it is the first step in learning how they work.
This article is meant for those who have used membership system in classic sites and want to learn about client-side sessions and the principles of the OAuth protocol. It will cover major shifts in session management which took place over the last decade:
- From Cookies to Tokens
- From SAML to JWT
- From Server to Client sessions
- From Statefull to Stateless sessions.
This article is divided into 3 parts:
- Part 1 – When the cookie crumbles: talks about why we started using cookies for session management and the problems of that technique. Topics: Session id cookies, Server-side sessions.
- Part 2 – Keeping state local: explain client-side sessions and when we should (and shouldn’t) use them. Topics: Tokens, Claims, JWT, Client-side session implementation, and State-full vs State-less sessions.
- Part 3 – Designing for scale: talks about how leveraging new technologies like Browser Storage and NoSQL High-Availability Databases together with a client-side state can help our sites meet new availability and scalability demands.
Membership system generally contains 3 major component: Authentication (including SSO), Session Management, and Authorization. However, this article only focuses on Session Management. In order to understand how session management relates to the other components, it is recommended to read my other article in the subject: Fifty Shades of Membership Sites: Mixing Authentication, Authorization, Session Management and Single Sign-On.
Disclaimer: This article discusses a technique of moving from server-side to client-side sessions. This technique not suitable for all sites – it’s mainly intended for Single-page applications (SPA) and modern API based sites, and require specific architecture changes (which are fully explained). However reading this article is still recommended for everyone dealing with sessions and claims, because it covers those topics in depth and it is a good place to start entering the OAuth world. Have fun!
Part 1 – When the Cookie Crumbles
In order to understand client-side sessions we need to understand why they were created – what kind of issues were they trying to solve.
For this, we first need to learn how sessions were managed before that, and what kind of problems they had.
So let’s begin with the most basic question: What is Session Management, and why do we need it?
Session management is a technique which sites need to use when they want to offer their visitors the option to become “members”. It is part of the site’s membership system, which manages the site members. Becoming a site member allows visitors to have personal benefits (for example premium content and customizing site experience) or the ability to interact with other people via comments and forums. In order to provide all of these capabilities, the site has to know who is making the request, which is where the membership systems come into play.
The two major parts of membership system are:
- Register: allows visitors to create a user on the site by entering username and password
- Login: allows users to log in to the site by entering their username and password
Registering a user usually works by creating a user inside the user’s table (including the password in a secure way). After that phase, the users can use the login form in order to sign into the site. The idea is that after using the login form, all operation users perform on the site will be associated with that user. For example, when they leave a comment, the site will know who is creating the comment since they are logged in.
But that idea had one critical problem: The basic protocol of the web – HTTP – is stateless. This means that every request coming from the user browser is not related to the previous request they made – it could have come from a completely different user. And without a way to associate between requests and users, servers couldn’t differentiate between users, preventing the existence of membership sites.
And this is why the concept of Session was invented. The idea is that after using the login form, the server would send back a unique piece of information for that user browser. And from that point, when the browser is making requests to the site, it will attach that unique piece of information, which will allow the server to associate the request with the user.
The first mechanism which was used in order to manage user sessions was the built-in cookie container which all browser supported. Cookies work in a simple fashion: They are a small text file which, once received from the server, are sent by the browser in all request which goes to the same site (according to the site domain). After a successful login, the server issued a cookie to the browser, and then it looked for that cookie in any further request in order to identify the user associated with the request.
This technique was so common that it was baked into many major website development frameworks, alleviating developers the need to implement such system. But even if web developers were not aware of it, understanding how cookie-based session work is still important, especially when learning modern authentication protocols.
This method, of using cookies for session management, was very popular for a time. But as the more people began using the web and site started handling large traffic, some problems started to appear. And when users started to use the web from multiple devices (like mobile apps), the limitation of this technique became clear, and a new way was needed.
In order to understand those problems, we need to dive deeper and learn about the implications of using session ID cookie.
Session ID Cookie
In the classic forms based login, the user has presented a form (page) asking for username and password. After submitting those values to the server the user is issued a cookie. The browser then sends that cookie with further requests in order to identify it’s user to the server.
But what does the cookie contain? In the classic scenario, the cookie contains a single unique identifying number, called Session ID. This Session ID is generated by the server when first creating a session, and since we are using cookies it is returned to the server in every request.
The Session ID Cookie allows the server to associate different request to a single browser. But just knowing that all those requests come from a single user does not help us know who that user is – what’s their name, email, or their primary key in our database.
In order to know which sessions belong to which users, we need to keep a list of all session IDs and the details about their user. This is called the session state, which usually contains important user profile fields. Session state is used inside membership area pages in order to display users their profile information and to link any action they do to their account.
So where do servers keep the session state? Well, there are a couple of places. The most common option is to store it inside the server memory (RAM), but it can also be stored inside a database or a distributed cache.
The Problem With Cookies
This method, of using cookies to store session ids and storing the session state on the server, have some major disadvantages. We can summarize them into the 3 broad categories:
- Sessions based on Session ID cookies are not stable and hard to scale
- Cookies are not naturally supported by mobile and desktop applications
- Cookies only work for a single domain
Sessions Based on Session ID Cookie Are Not Stable and Hard to Scale
The simplest Cookie Session ID implementation is to store the session state in the memory of the process which handles the request (
in-proc). This method has the benefit of very fast session lookup time (after all, it’s inside the process memory) but comes with many problems:
First, what happens if the server is recycled or reset? Not only that we lose all of the user session states (bye bye shopping cart) but worse – we created a situation where the client and the server session are not in sync (the client still thinks he is logged-in but the server doesn’t know that). This can cause problems when the client submits new requests based on a session that does not exist anymore. It is hard to keep the client-side session and server-side session in sync, which can cause instability.
Second, process memory is not shared between different application. This means that only the application which created the session can access it, which can make it difficult to support real-world scenarios:
- When upgrading an existing website it is usually done in several phases. This means that there is a time when different parts of the site are served by different frameworks. In such cases, it’s not possible to access user session that exists on another server, which makes keeping the user logged in through the entire site very difficult.
- When adding API/REST support to the site, it usually sits in a different application from the primary site. Sometimes it is even powered by a different framework or technology.
- Many large and modern sites are usually a mix of different technologies, for example, the membership area is hosted in a different application or framework.
In all of these examples, even though the session cookie is served to all applications (because they share the same domain), they do not share the same process memory meaning they will not be able to access the session state.
The third and most important problem is scalability. Maintaining session inside the server memory works well if we have a single front server. But as soon as we need more fronts (to handle the larger traffic) we start encountering issues with our server-side session – when a user logs into one front and we create a session in that front memory, what will happen if the following requests will go to another server? Since the session only exists in the memory of the first server, we won’t find the user session state which will cause the request to fail.
In order to handle multi-server site architecture (clusters), a number of techniques were introduced during the past years, all with their own issues:
- Sticky Session: Load-balancer feature that makes sure that if a specific server started serving a user it will continue to do so for all the following requests (also known as server affinity or persistence).
Cons: Require load-balancer that support it (not everyone can afford one); can malfunction (premature session expire, company proxy and more); can hurt load-balancing because it forces the same servers to handle specific IP traffic without regards to the actual server load; makes your application depends on IT for scalability; also now we need to synchronize between 3 sessions: client session (cookie with id), load balancer session and server session, which makes it very likely to fail at some point. But the biggest problem is that the Load-balancer acts as single point of failure – if it stopped working the entire site is down. You can read why sticky session are evil here.
Shared Database: We can store all of our session state inside a single database and make all requests retrieve the data from it.
Cons: The need to fetch the session state from the database for every request can cause performance issues; can be unstable because of concurrency issues and database connections problems; hard to implement and require constant technical support from IT and DBA experts. Because of those problems, this method is rarely used these days. Also, like sticky session, the session database is a single point of failure – if it stopped working the entire site will go down.
Distributed Cache: Session state is stored in a special server memory which is replicated to the other servers memory.
Cons: Require integrating with special software which complicates the architecture (in production and in development environments); synchronizing memory between all the servers in real time requires very good connection between them, especially when dealing with many users and large amounts of session state; also takes processing power that instead could have been used for running business logic; need to open ports and handle large amounts of chatter between the servers; require good IT support with high maintenance costs; also what happens when a server is restarted? Bottom line: a complicated solution that may work for some but not for others, hard to maintain it and can cause many stability issues.
As we can see all of these techniques have and two major problems: They all depend on external systems and all require some kind of servers synchronizations.
This makes them A – Not stable:
All our fronts need to be able to communicate with each other or access a single shared resource (the database, for example). In a confined 2-3 fronts setup this can work, although not without issues – what happens if a front dies and comes back later? what happens if the communication between the servers is temporary interrupted? We either have to deal with the added complexity of synchronizing servers or risk a single point of failure.
And B – Not scalable:
The real problem starts when we want to scale up the operation and move to the cloud, where servers are added and removed dynamically from the cluster based on traffic and load. Trying to keep session state in sync between a dozen of servers in a dynamic environment where they can go down and up all the time, makes managing session state in server side not practical for large sites. In fact, according to the new micro-services architecture, it is recommended to try avoiding using server state (session) as much as possible.
Cookies Are Not Naturally Supported by Mobile and Desktop Applications
Also, many times using cookies for server-to-server communication can be difficult, not practical and sometimes not even possible and usually considered bad practice.
Cookies Only Works for a Single Domain
Cookies are only sent to the domain they were created in, so they can’t be used for authentication/authorization across different sites:
- If we want to provide identity service and support single-sign-on (SSO) to other sites (like “login with Google”) this can’t be implemented with cookies.
- If we want to provide REST API services for external sites (for a B2B purpose) which require client authorization this can’t be done with cookies.
The Way The Cookie Crumbles
As we can see, cookies based session has a very limited usage scenario: we can basically only use them in our site where we control everything – the client application (which can only be a browser), the identity provider (which must be our site) and the resource server (which must be in the same domain as our site).
It is clear that cookies-based session management has reached its limit and is no longer suitable for use in modern sites. A new way to manage the session was needed – and that was is Claims Based Authentication.
In Part 2 (Keeping It Local) we will learn how Claims Bases Authentication has solved many of our cookie problems and meet our cookie replacement – the Token.