1 Introduction
Before starting, note that I intend to expand this article to include more topics with time, this article whill go through many changes.
Consider a user engages with an e-commerce application. The user logs in with their credentials, then add something to the cart, scrolls around a bit - sees a section that shows previously browsed items and finally concludes by logging out (or maybe just closing the browser). Now when they login again later, they see the item is still there in the cart.
HTTP by itself is a stateless protocol, meaning each request is a fresh new connection between server and client. Then how do the sites know whether a user is logged in and has objects in the cart? In other words, how does server know the state of the user during a session? Thankfully there are workarounds to provide for the stateful needs using the stateless protocol.
2 Sessions
A session is a long running interaction between a client and a server. It may consist of a single request, but more commonly it consists of a series of requests that the user regards as a consistent logical sequence. (Fowler 2012)
A site has to keep record of these sessions and we will see how these sessions are stored and used to deliver the content to the user. Somehow, we have to make the app aware of whatever the user is doing during the session or has done previously. Now, this means with each HTTP request we have to send and receive a lot of data but with proper session management this reduces to sending a small session ID/token that helps retrieve the associated data. A session may initialise, modify a lot data objects for a user and we can tie this to a session ID for session management.
There are two ways to store the session state:
- Client session state
- Server session state
- Database sessions state
Regardless of the method used, typically a session ID is transferred back and forth with HTTP requests in the form of session cookie. This cookie is stored in the client side.
2.1 Client Session State
As evident by name, this stores the data on the client. In some cases, it is possible to keep all session data on client and with each request this entire set of data is sent back and forth between server and the client. This can be done using : URL parameters, hidden fields/forms and cookies.
2.1.1 URL Paramters
Example : somesite.com?id=87654321
This is easy for small amout of data and if security is not a concern. While, keeping on adding more data increases the URL length, which is mostly not desirable. Also, URL rewriting may cause problems with bookmarks in client side.
2.1.4 Web Storage
There’s mostly two types of session storage to work with - session storage, one that spans for a session and then expires and persistent storage which stays for much longer duration before expiring (as we have seen for cookies and more to come). Browsers offer a Web Storage API which allows for preserving non-essential state across requests and even session. Specifically this API offers two storage objects :
localStorage
: This persists between browser sessionsessionStorage
: This stores data that will be lost after browser session is over.
Most browsers provide these Web Storage APIs which have storage capacity of much larger than that cookies (5-10MBs per origin). However, unlike cookies the stored here isn’t communicated back with HTTP requests. Hence, it is not used as a replacement of cookies, instead it is used as a local cache to store static files downloaded sent by server to reduce server load for subsequent requests. Additionally, these are more prone to XSS attacks.
2.1.5 IndexedDB
This is not your usual relational database to store persistent non-essential session data, instead this is an object-oriented database that provides asynchronous access - it lets you request to store and retrieve data objects. It can store large amounts of data structures including files/blobs and index them for high-performance searching. IndexedDb also follows the same-origin policy.
2.2 Server Session State
Keep the data in server memory keyed by a session ID sent by the client. Although this will be very fast, secure against client tampering and easy to implement; could strain memory. To relieve this, we can use local file system which makes the entire process slightly slower. Additional things to take care of : garbage cleaning, expiring sessions with background jobs, rotating session IDs. However one problem still remains - if the server crashes, the session data is lost. Using multiple servers requires a shareable storage which brings us to our final method - database session state.
2.3 Database Session State
Session data is stored in a shared, persistent, typically relational database. Server upon receiving request from client, uses the session ID to retrieve data from the database storage and then serves the request. Although this comes at a cost of increased latency, it works against server failures and supports clustering.
To make the distinction between session data and longer persistent data, one may use separate tables - and which is better since, it might be better to distinguish between in-process order and completed order during a transaction session – a session data must be committed to become persistent.
3 Caching
We can store data temporarily on the memory to improve performance of the application - known as caching. This can be done on both client and server side. And even on server side, we can use a distributed caching system, that can be accessed by more than one server using systems like Redis or Memcached.
Caching is important in both server and client side. For example, if you have to render a page thousand times per second to serve requests, you can use the cache to serve subsequent requests from the memory. There are two categories here:
- Page Output Caching : This refers to caching the output of a page, rendered page or just its parts are saved for better performance. Judicious use is advised, caching every page might not be as useful, instead cache pages that have high visits.
- Application Data Caching : For data intensive applications, and for those which affect the latency retrieving the data again and again from database, it can be cached into memory, pages requiring same data can use the cached version. Remember, to check for any concurrency issues here.
The application has to make sure the cache gets invalidated and refreshed when underlying data changes.
4 Sessions and JWTs
We have seen that typically a good method would be user information being stored on server memory or database and exchange of session ID using cookies or URL helps maintain the session. Another way, very common in modern system is the usage of tokens, which is towards a stateless approach. “Stateless” here means that session data is not stored on server side and the tokens themselves contain all session data, typically in JSON Web Token.
After a user authenticates successfully a token is issued, and the client may store the token in sessionStorage or cookies and send it with future requests for verification. The verification is often handled by a third party. This stateless approach is scalable, as it relieves the problems with storing session IDs in cookies such vulnerabilities and scaling issues with distributed servers, as well as performance issues. A JWT has following three things:
- Header : This contains metadata about type of token and the cryptographic algorithm used
- Paylod : This contains claims which are data assert about a subject. (e.g. name of a user)
- Signature : This is used to verify a token to ensure it is valid and wasn’t tampered.
The contents of JWT are serialized as Base64 URL-safe encoding. So, while JWT can be decoded and anyone can see the contents, they modify it without invalidating the signature.JWTs are not encrypted be default but can be encrypted in case of sensitive information using JWE (JSON Web Encryption). In gist, JWTs are signed and sent over and over with HTTP and asynchronous requests, data is encrypted while its being transmitted (so no one can read even after they intercept traffic). The server only verifies the signature and doesn’t store the token, they trust the token’s claims once its verified, JWT carries all the session data itself.
5 Remember Me?
Many sites offer a “remember me” checkbox, which persists the login for a long duration even throughout multiple sessions (hours or weeks later, you’re still logged in!). Usually one can store a long token value as a persistent cookie and on the server side, a hashed and salted version of this tokein is saved referencing the user account. This also contains an expiration date. In future vists the server checks the token and if it is presents allows log in and creates a new token which replaces the old one for the same use. While most sites, don’t just do this, intead they couple this technique with other methods like JWTs, SSO, it is still widely used for minimalistic logins.
6 Best practices
- Secure Cookies : As state earlier, uise flags like
HttpOnly
,Secure
andSameSite
to prevent XSS, force HTTPS and block CSRF attacks. - Avoid Server Affinity : Avoid tying data to one server, instead look for distributed architecture to avoid losing data on system failures.
- Session Cleanup : Expired sessions should be cleaned periodically to rpevent memory bloat.
- Rotate IDs/Tokens : Issue a fresh session ID/token on performing sensitive operations.
- Avoid overloading sessions and cache memory with unnecessary data, use judiciously.