Authentication is one of those topics with a lot many options and each has its own tradeoffs. When I began my journey as a software engineer, I was boggled by this topic too.
Just like different kinds of databases have their own purpose, authentication methods also have their own place. Today, let’s talk about the two most popular authentication methods and their pros and cons.
When you visit a website, where they ask you to enter a username and password, you only enter it once. How terrible it will be if you have to enter them on every page that you visit, right?
How does it happen? Sessions can enable this behavior.
🧠 How do sessions work?
When you send your username and password to the server for authentication, following happens:
1. The server creates a unique key and associates it to the user, that’s you, and stores it in the database or cache. The key also has an expiry time after which it won’t be valid.
2. This key is sent back to the client (the browser, app etc.) and the client stores it safely. This will usually be as a cookie.
3. With every call that the client makes, this session key is also sent along with the request. The server checks for the expiry of the key and validates it, letting the user perform the required actions.
The best part about this approach is that it’s easy to invalidate the session of a user when required. In case, the key is stolen or the user hasn’t accessed the key for a while, you can delete the session entry in the database and the key won’t be valid anymore.
This is called a stateful authentication method!
The server saves the state of the session by associating it with a user. Without the state, the user cannot be authenticated.
This can be a limitation at times.
When you are scaling your application, you will have to copy the state all across the scaling parts.
If you are saving the session as a database session, when you scale the database, you will have to replicate the session data as well.
If you are saving the session as cache in the server, which is a much more efficient method, then while scaling your servers, you will lose the cache data as it’s not convenient to duplicate the cache data.
Considering the limitations of stateful authentication methods, JWT or JSON Web Tokens came into the picture.
JWT’s philosophy was simple. The server doesn’t have to store the state of the user. A token should contain all the information required to identify a user.
🧠 How does JWT work?
1. Initially, it starts by sending the username and password to the server just like in the session based method.
2. After authentication, the server, creates a token for the user. But instead of saving it in the database, it makes sure that the token contains enough information to identify the user, such as their username.
3. The token is created by encrypting the data using a key or a password. If you have to decrypt it, you will need the password. This is to make sure that no one tampers with the contents of the token.
4. This token is sent back to the client. The clients stores it and sends it to the server with every request, usually in the header of the request.
5. The server on receiving the token, uses the password to decrypt it and gets the user information stored in it.
A JWT has 3 parts to it, and each part is separated by a dot (
- Header: This contains information about the algorithm used for encrypting the token
- Payload: This contains the additional information required to identify the user, such as username. This is also called claims because it gives the information of who the user claims to be.
- Verify Signature: This part is responsible for verifying the signature of the token, i.e. if the message was tampered by someone while transmitting or while it was stored.
This looks like a great alternative to stateful authentication but it has its own limitation.
While JWT is considered to be a stateless method of authentication, it cannot truly be stateless.
Consider a situation where a user token is hijacked by a hacker. The hacker can pretend to be the user and do things on their behalf. And the server cannot do anything about it since it doesn’t have a stored session to refer to!
You can definitely add expiry to JWT, but that is still not ideal. You don’t want to expire the token too soon or let it be valid for too long. Deciding the ideal time will be difficult.
This issue can be tackled using two methods.
One, you can maintain a blacklist of tokens that are revoked (e.g. if the user logs out) and not allow them in future requests.
Two, you can use the concept of refresh tokens. This is much like sessions where the server stores a refresh token associated with a user. JWT will be a short-lived token and upon expiry, uses the refresh token to generate a new JWT.
When the client requests a new JWT, the server checks the refresh token and generates another short-lived JWT associated with the user. This JWT can be used for further requests.
As you can see this is not truly stateless since you need to maintain some state in the database.
The above-mentioned methods simply provide a mechanism to authenticate. Another aspect to figure out is where and how to store the tokens/keys and how to send them to the server. You can opt for a cookie-based approach, where you save the tokens in a cookie and send it in the cookie header of the request.
Similarly, you can save it in the local storage and send it in a custom header or the authorization header of the request. This method is less secure and is not used much.