Using Session Cookies Vs. JWT for Authentication
What is JwtToken (JSON Web Token) and how does it differ from an older concept of a cookie?
On Stack-Overflow I saw a couple of questions about “JwtToken vs Cookie” or “JwtToken instead of Cookie”. The fact is, JwtToken is not a direct alternative of a cookie, but rather of a SessionId. What does it exactly mean? It means, you can store the JwtToken in a cookie, just like you would store a SessionId earlier.
The below image shows a JwtToken stored in a cookie named “access_token”:
The advantage of a JwtToken over a SessionId is that JwtToken has a built-in validation mechanism. For example when validating the token in your backend, you can check whether it has been tampered with.
This is a sample backend validation of the jwtToken:
Also you can store user id in the token itself.
The most important advantage is JwtTokens are stateless, since they contain all the necessary info for validation, so you don’t need to store them in a db, like SessionId. Whereas sessionIds need to be stored so they can be later validated. This is especially useful when you have distributed systems where nodes don’t necessarily share a db or some other storage.
There are some UX standards that define expiration times for both JwtToken and cookie. As always, there is a tradeoff here between user convenience and security. Standard is to set JwtToken validity to a week, while refreshing the JwtToken and prolonging the validity every 15 minutes or so. The reason for refreshing token is to refresh the access rights attached to the token frequently in case they change.
Important note here: anyone who has a JwtToken can actually decrypt it online on https://jwt.io/. So, make sure NOT to store any sensitive data inside JwtToken. Because if user’s password is stored in a JwtToken and a hacker would steal it, they wouldn’t only steal user’s session, but also the credentials.
The token below, contains email address, issued-at and expiration dates, which you can read from the decoded payload on the right:
Also using JwtToken might become a disadvantage if you store a lot of data inside it. Since it’s sent back and forth with every request, it can slow down the communication, whereas with the sessionId you are just sending an Id and looking up the data on the server side, which makes the transfer over the network faster.
Long-lived vs short-lived token
It’s also important to get some wording about the token type straight:
Long-lived (refresh) token is to prove your authentication: you are a valid user. Short-lived (access) token is there to prove your authorization: your valid user has certain access rights.
This means, the long-lived token needs to be refreshed when you change your password, username or log in again, since you will need to re-prove your authentication. Whereas upgrade to premium membership should lead to refreshing short-lived token, since the user now has more access rights and thus the authorization has changed. However, If you have a mechanism in your application that validates your access to premium features without relying on JwtToken, than you won’t have to refresh the token for that particular case.
But generally, you will have to refresh both short and long lived tokens to make sure they are invalidated after some time, so that if someone stole it, they could not use it with the old authentication or authorization data.
Why do we need the JSON Web Token (JWT) in the modern web?
Once upon a time
Suppose you have a REST API (e.g.
GET /orders) and you want to restrict access to authorized users only.
In the most naïve approach, the API would ask for a username and password; then it will be searched in a database for whether those credentials really exist. We check for authenticity. Finally, it will be checked if the authenticated user is also authorized to perform that request. If both checks pass, the real API will be executed. It seems logical.
A problem of state
The HTTP protocol is stateless, this means a new request (e.g.
GET /order/42) won’t know anything about the previous one, so we need to reauthenticate for each new request (fig.1).
Fig. 1 — Due to the stateless nature of HTTP protocol, every new API request needs a complete authentication.
The traditional way of dealing with this is the use of Server Side Sessions (SSS). In this scenario, we first check for username and password; if they are authentic, the server will save a session id in memory and return it to the client. From now on, the client will just need to send its session id to be recognized (fig.2).
Fig. 2–Using SSS, we reduce the number of authentications towards the Credentials database.
This solution will fix a problem but it will create another one.
The better solution (Modern approach)
JWT is a token based stateless authentication mechanism. Since it is a client-side based stateless session, server doesn’t have to completely rely on a datastore(database) to save session information.
Communication is safe because each token issued is digitally signed, so the consumer can verify if the token is authentic or has been forged.
Each token is self-contained, this means it contains all information needed to allow or deny any given requests to an API. To understand how we can verify a token and how authorization happens, we need to take a step back and look into a JWT.
Anatomy of a JWT
A JSON Web Token is essentially a long encoded text string. This string is composed of three smaller parts, separated by a dot sign. These parts are:
- the header;
- a payload or body;
- a signature;
Therefore, our tokens will look like this:
The header section contains information about the token itself.
The following JSON explains which algorithm has been used to sign the token (
alg) and which is the key (
kid) that we need to use to validate it. One moment of patience, please, we will look into this soon. :)
The JSON is finally encoded as Base64URL:
eyJraWQiOiJ -TRUNCATED- JTMjU2In0
In a nutshell:
- HTTP protocol is stateless, that means a new request won’t know anything about the previous one;
- Server Side Sessions was a solution to the statelessness of HTTP, but these, in the long run, were a threat to our scaling abilities;
- JWT is self-contained, that means it contains every information needed to allow or deny any given requests to an API;
- JWT is stateless by design, so we don’t have to fight with the stateless design of HTTP;
- JWT is encoded, not encrypted have it in mind;