Using Redis to maintain in-memory user sessions with JWT
Tags: auth, redis, jwt, session
Published: 2022-10-08 17:02:16

### Use Case 

Imagine you are building the next Amazon, which allows users to order anything via the mobile / web app. 
Users will login into the app, they can browse the product, add items to the cart, and then checkout to get their favourite things delivered to the doorstep. 

To allow users to browse the products or add items to the cart, the app will make several REST API calls to the backend using the HTTP protocol. Since HTTP is stateless, and we can’t ask the user to log in again and again, we need a mechanism to provide user context with every call, which the back-end can use to determine which user is interacting with the application. 

This is where user sessions come in. User Session contains pieces of information about the users, that allows to identify frequently used information about the user, for a given time-frame till it is valid.


It can contain data like the user's email, roles, preferences, etc.

Please note that maintaining data in session can be expensive, insecure and hard to scale, hence we need to be very careful about what to put in there

### How do Sessions work?
We need to store user sessions somewhere where we can use that in every request.

We need to be able to trust this information. For example: If we are storing the roles in the session, we need to be sure that role information has not been tampered with on the client side, as the client's information might be used in authentication and authorization. 

Hence we can store the session either on the client side or on the server side.


#### Storing session at the Client Side. 
If we want to store the session data on the client side, whenever the user logs in we create a user session and send it back to the client. There are multiple ways to do that but we will be talking about JSON Web Tokens (JWT in short) for the purpose of this article. 

##### What is JWT ?

JSON Web Token is essentially a long string that can be used as a way for exchanging information between two systems as a JSON object. Since the token is signed using an algorithm (like HMAC), we can verify the signature using a secret key and if it is valid we can trust the information contained in the token. 

JWT consists of 3 parts

1. Header
2. Payload
3. Signature

JWT is represented as 
```
<header>.<payload>.<signature> 
```
Here is an example JWT token. 
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
The contents of this on any other JWT can be viewed on https://jwt.io or a similar debugger.

More info about the JWTs at https://jwt.io/introduction

Here is how our use case would look with JWT: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u82dgp20gmxoqw3kro4w.png)


1. User signs into the app by using his credentials. 
2. The server authenticates the user by querying the database. 
3. Server generates a JWT token using the required user’s information and the signing secret available at the server. 
4. The server then sends back the JWT token in the response headers/ cookie to the client. 
5. When the client sends another API call this token is passed in the Authorization header/ cookie.
6. Server extracts the JWT token from the header/cookie and validates the token using the stored secret.
7. If the validation was successful, the server responds with the requested resource, otherwise, the server responds with a 401 error.


**Advantages**

1. Eliminates database calls :  Since the required data is stored in the token itself, it completely eliminates the database calls to get that data.
2. Easier to scale : Since the state/ data is passed in the request itself, it is independent of the number of servers and this solution can be scaled easily.

**Disadvantages**

1. Cost : Amount of data exchanged between the client and server will grow as we add more data in the session and will consume more bandwidth for the server. Since the user is constantly interacting with the app, this data should be exchanged frequently between the client and the server, thereby increasing costs
2. Security : Opens Possibility of Security loopholes if not implemented correctly. Someone could take advantage of the security vulnerabilities in the implementation of JWT verification and use that to tamper with the session data, may try to act as admin and try to access restricted resources on the backend.
3. Stale data : we can’t maintain any data in the token that can potentially go stale. Consider that we are storing the user preferences in the token, and the user is logged into multiple devices at the same time. Since the token lives on the client. The changes that were made by the user on one device client, won’t get reflected on the other device.
4. Expire Invalid tokens : How to expire/invalidate the session that has been sent to the client. Consider a scenario where the user's password has been compromised. He goes and changes the password on our system. But the token which is present on the hacker's computer still has not expired. How do we prevent that token from accessing the data? The only way to do this is to maintain some state at the backend. Maybe a list of blacklisted tokens or the solution we are going to discuss soon.
5. Session Size : Depending upon the mechanism of storing or exchanging the session data, there are limits imposed by the clients/browsers. For example:
    a. Browsers generally support a max cookie size of 4KB
    b. Max header size is also generally restricted while 
    sending the request to the server, although it could be 
    modified to allow bigger headers but still not 
    recommended to save network bandwidth.

##### Security considerations while storing the user sessions on the client side 

Proper care should be taken when you are implementing the sessions on the client side, to minimize the security risks and prevent any unwanted threats and attacks.

1. Totally avoid exchanging any sensitive information over an unencrypted channel based on HTTP. The complete communication between the client and server should be always encrypted over an HTTPS channel. 
2. If we are storing the sessions in the cookies, we should properly use the secure and HTTPOnly flags. More info on this can be found [here](https://resources.infosecinstitute.com/topic/securing-cookies-httponly-secure-flags/) 
3. We should have a small expiration time on the cookies, so that they will automatically expire after that duration of inactivity.

#### Storing Session on Server

Due to the potential security and vulnerability issues in storing the session at the client. We can go with a more traditional approach of storing the client data at the back-end.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jf873gvleeinzpjjfthy.png) 

1. User signs into the app by using his credentials. 
2. The server authenticates the user by querying the database. 
3. The server then creates a session id ( a random opaque string ), and stores a session object in the database against that id. Here is what a piece of typical session information would be like: ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lpxg2129n9nohvm91yzo.png)
4. The server then sends back the session id in the response headers/ cookie to the client. 
5. When the client sends another API call this session id is passed in the request.
6. Server takes the id and retrieves the session from the session store.
7. If the session information was valid, the server responds with the requested resource, otherwise, the server responds with a 401 error.

**Advantages**

1. Full control over the session data : Since the session lives on the server side, we have full control of managing it. 
   a. We can invalidate the data when a user logs out, changes the password, or his account is compromised. 
   b. We can have control over the max number of active sessions a user can have at any given point in time.

2. Can store large amounts of data : On the server, we can store relatively large amounts of data, and is not limited by browser or client level restrictions like 4KB restriction on cookies, etc.
3. Secure : Only the server can access and edit the user session data, by having the required validations in place, the data is implicitly secure and can be trusted and used.
4. Analytics : Since we have access to the user session data, we can run the required analytics on the user data. 
 
**Disadvantages**

1. Hard to Scale : Since the request can land on any of the servers on the backend, we need to maintain a global state which can quickly become a bottleneck. We can apply different schemes like sticky sessions, which send the same user requests to the same server, but still, there is no guarantee.
2.  Cost : Maintaining a state at the backend is costly in terms of maintaining a global state.

If you have ever worked with a J2EE web application using HTTP you must have seen JSESSIONID being used for session management. The server creates and sends the JSESSIONID cookie to the client and then the client sends it back to the server in subsequent HTTP requests.
 
##### Security considerations while storing the user sessions on the server side.


1. Session tokens should be long and random enough and not predictable at all, to prevent hackers from using brute force mechanisms.
2. Also for each new session, we should get a unique id or hash to avoid any conflicts between two user sessions.
3. Sessions should be set or reset properly at each logical event like user login and logout, password reset, or session time expired event.
4. If you are using a library or server-managed sessions be sure to research underlying implementation, track the new releases, and patch updates as soon as possible.

#### Hybrid approach

We saw two approaches for storing the user sessions at client sessions, we can also use a somewhat hybrid approach, which combines the advantages of both approaches and will try to solve any bottlenecks we have discussed.


We will use Redis to store the session data acting as a global store for storing the session data. It provides great performance with read and write operations in milliseconds or less.

Here is a sample implementation, we will be using two sets of key-value pairs to store the session information on our Redis cluster, in order to fulfill our use cases.

1. sessionId => userId : This stores a unique session Id as key and the userId as value.
2. userId => [sessionId1, sessionId2] : It is a Redis Set, which is a collection of unique string(session keys) elements. It is highly efficient as it provides O(1) time to add, remove, and test for the existence of members. It gets an entry every time the user will sign-in into the application where the key is added as the user id and value as the sessionId
3. userId => sessionObject : This stores a unique session Id as key and the actual sessionObject as value.  
4. We can still maintain other non critical information in the JWT, if we would like to.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0m0twns1ciogtkkot5jf.png)

1. User signs into the app by using his credentials.
2. The server authenticates the user by querying the database. 
3. The server then creates a unique sessionId, stores the data in the Redis cluster, and creates a JWT session token that stores this sessionId.
4. The server then sends back the JWT token in the Authorization header or secure Authorization cookie to the client. 
5. When the client sends another API call this token is passed back to the server.
6. Server takes the JWT token and validates the token using the stored secret.
7. If the validation was successful, the server uses the sessionId from the JWT token to extract the information out of Redis. the server responds with the requested resource, otherwise, the server responds with a 401 error.


Let's see how it solves our use cases. 

1. We have the required information stored in the Redis and the JWT Token.
2. Since the session data is stored on the server side, We have full control over the critical session data. Nobody can tamper with it.
3. If the user resets its password we can simply remove all the existing sessions from Redis and the user will be logged out from all devices.
4. We can have a limit on the number of sessions across the devices.

### Conclusion

We have learned about different ways to manage, control and implement user sessions.

Session management must be taken very seriously by the developers and management of any organisation as incorrect implementation can potentially lead to serious security risks to the application potentially compromising the privacy and integrity of customer's and organisation's data. Developers should spend time researching the best practices and understand the possible risks in their session management implementation. 

Checkout the following resource on Session Management: 
https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html

This post is in collaboration with Redis.
 
Learn more
 
[Try Redis Cloud for free](https://redis.info/3NBGJRT)
[Watch this video on the benefits of Redis Cloud over other Redis providers](https://redis.info/3Ga9YII)
[Redis Developer Hub - tools, guides, and tutorials about Redis](https://redis.info/3LC4GqB)
[RedisInsight Desktop GUI](https://redis.info/3wMR7PR)