- Published on
Session and JWT
- Authors
- Name
- Tien Minh Pham
- @TinMinhPhm1
Introduction
Today, we are diving into the world of web authentication. We’ll explore the two most common approaches: session-based authentication and JSON Web Token (JWT). We’ll walk through the flow of each mechanism and discuss their pros and cons. By the end, you’ll have a clear understanding of when to use each one. Let’s get started.
First, let’s walk through the flow of session-based authentication.
User Login Process
- The user sends their login credentials to the server.
- The server verifies these credentials.
- If they’re valid, it creates a new session.
- The server then stores the session data, typically in a database or in-memory cache like Redis. This data might include:
- User ID
- Session expiration time
- Other metadata
- The server sends back a response with a unique session ID, usually in the form of a cookie.
- On subsequent requests, the client automatically sends the session ID cookie with each request.
- The server takes the session ID, looks up the corresponding session data in its session store, and uses that data to authenticate and process the request.
Session-Based Authentication
The key point is that with session-based authentication, the server is responsible for creating and storing the session data. It then uses the session ID as a key to retrieve this data on future requests.
Advantages of Sessions
One advantage of sessions is that revoking a session is straightforward. Since the session data is stored on the server, the server can simply delete or invalidate the session at any time.
Challenges in Distributed Systems
However, in a distributed system where your application runs on multiple servers, all those servers need access to the same session data. This is typically achieved by using a centralized session store that all servers access, like Redis
or a distributed SQL database. While this works well, it does add some complexity and potential latency to each request, as the server needs to make a separate trip to the session store.
Now, let’s look at the flow of JWT-based authentication.
- First, the user sends their login credentials to the server.
- The server verifies these credentials.
- If they’re valid, it generates a JWT.
- The server signs the JWT with a secret key. This signature ensures the integrity of the token, preventing tampering.
- The server then sends back the JWT to the client, typically in the response body.
- The client stores the JWT, usually in local storage or a cookie.
- On subsequent requests, the client sends the JWT in the request headers.
The server verifies the JWT signature. If it’s valid, the server trusts the data in the token and uses it for authentication and authorization. The critical difference here is that with JWTs, the server doesn’t store any session state.
All the necessary data is contained within the token itself, which is stored on the client. This makes JWTs stateless.
For signing JWTs, there are several algorithms available, with HMAC, RSA, and ECDSA being the most common. HMAC is a symmetric signing method, which means the same secret key is used to sign and verify the token. This is simpler and more efficient, but it requires sharing the secret key with any service that needs to verify the token, which can be a security concern.
RSA and ECDSA, on the other hand, are asymmetric signing methods. They use a private key to sign the token and a public key to verify it. This allows for a more secure architecture where the private key is kept secret and only used for signing, while any service can verify the token using the public key. However, this adds some complexity and computational overhead compared to HMAC.
The choice of signing algorithm depends on your security requirements and system architecture.
If you have a monolithic application or trust all the services in your system, HMAC might be sufficient.
But if you have a microservice architecture or need to share JWTs with untrusted third-party services, RSA or ECDSA provide a more secure solution.
One challenge with JWTs is handling token expiration. If a token is stolen, it can be used until it expires. To mitigate this, you can use refresh tokens in combination with short-lived access tokens.
The access token is the actual JWT used for authentication on each request. It has a short expiration time, typically around 15 minutes. The refresh token, on the other hand, has a much longer expiration time, perhaps several days or weeks. When the access token expires, instead of requiring the user to log in again, the client can send the refresh token to a special token endpoint on the server. The server checks if the refresh token is valid and hasn’t been revoked. If everything checks out, the server issues a new access token. This process happens behind the scenes, without requiring interaction from the user. This approach strikes a balance between security and user experience. The short-lived access tokens limit the window of potential misuse if a token is stolen.
While the long-lived refresh tokens allow users to remain authenticated for an extended period without needing to log in repeatedly.
It’s important to note that the refresh token is only sent when the access token has expired, not on every request. The access token is sent on every request that requires authentication. So when should you use session-based authentication, and when are JWTs a better choice?
Session-based authentication is a good fit when you need the ability to revoke sessions instantly. If a user reports their account as compromised, you can immediately invalidate their session on the server side. Sessions are also a good choice if you already have a centralized data store for other purposes. In this case, you can leverage that existing infrastructure for session storage as well. However, it’s important to keep in mind that using a centralized session store does add some latency to each request, as the server needs to fetch the session data from the store. Finally, sessions keep sensitive data on the server, which can be a security advantage.
On the other hand, JWTs are a great choice when you need a stateless architecture. Because JWTs store all the necessary data in the token itself, your server does not need to keep track of sessions in memory or in a database. This makes it much easier to scale your application horizontally across multiple servers. JWTs are also useful when you need to share authentication data with other services.
For instance, in a microservice architecture, a JWT generated by the authentication service can be verified and trusted by other services without needing to contact the authentication service on each request. If you do choose JWTs, consider implementing refresh tokens to balance security and user experience. Refresh tokens allow you to use short-lived access tokens to limit the window of potential misuse, while still allowing users to remain authenticated for an extended period.
Ultimately, the choice depends on the specific needs and architecture of your application.