JWT: Understanding JSON Web Tokens
API security and authentication : Token mechanism for securing services
A JSON Web Token or JWT is made up of three parts:
The header: contains some metadata about the token itself.
The payload: contains the data that we want to encode into the token, so the more data we want to encode here the bigger is the JWT.
The signature.
These first two parts, the header and the payload, are just plain text that will get encoded, but not encrypted.
So anyone will be able to decode them and read them, we cannot store any sensitive data in here. But that's not a problem at all because in the third part, the signature, is where things really get interesting. The signature is created using the header, the payload, and the secret that is saved on the server.
And this whole process is then called signing the Json Web Token. The signing algorithm takes the header, the payload, and the secret to create a unique signature. So only this data plus the secret can create this signature. Then together with the header and the payload, these signature forms the JWT, which then gets sent to the client.
Once the server receives a JWT to grant access to a protected route, it needs to verify it in order to determine if the user really is who he claims to be. In other words, it will verify if no one changed the header and the payload data of the token. So again, this verification step will check if no third party actually altered either the header or the payload of the Json Web Token.
So, how does this verification actually work? Well, it is actually quite straightforward. Once the JWT is received, the verification will take its header and payload, and together with the secret that is still saved on the server, basically create a test signature.
But the original signature that was generated when the JWT was first created is still in the token, right? And that's the key to this verification. Because now all we have to do is to compare the test signature with the original signature. And if the test signature is the same as the original signature, then it means that the payload and the header have not been modified.
Because if they had been modified, then the test signature would have to be different. Therefore in this case where there has been no alteration of the data, we can then authenticate the user. And of course, if the two signatures are actually different, well, then it means that someone tampered with the data. Usually by trying to change the payload. But that third party manipulating the payload does of course not have access to the secret, so they cannot sign the JWT. So the original signature will never correspond to the manipulated data. And therefore, the verification will always fail in this case. And that's the key to making this whole system work. It's the magic that makes JWT so simple, but also extremely powerful.
Now let's do some practices with nodejs:
Configuration file is perfect for storing JWT SECRET data. Using the standard HSA 256 encryption for the signature, the secret should at least be 32 characters long, but the longer the better.
config.env:
JWT_SECRET = my-32-character-ultra-secure-and-ultra-long-secret
//after 90days JWT will no longer be valid, even the signuter is correct and everything is matched.
JWT_EXPIRES_IN=90
now install JWT using command
npm i jsonwebtoken
Example after user signup passing him JWT token so he can stay logged in and get access of resources.
exports.signup = catchAsync(async (req, res, next) => { const newUser = await User.create({ name: req.body.name, email: req.body.email, password: req.body.password, passwordConfirm: req.body.passwordConfirm, }); const token = jwt.sign({ id: newUser._id }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN, }); res.status(201).json({ status: 'success', token, data: { newUser, }, }); });
output:
Why JWT?
Instead of storing information on the server after authentication, JWT creates a JSON web token and encodes, sterilizes, and adds a signature with a secret key that cannot be tampered with. This key is then sent back to the browser. Each time a request is sent, it verifies and sends the response back.
The main difference here is that the user’s state is not stored on the server, as the state is instead stored inside the token on the client-side.
JWT also allows us to use the same JSON Web Token in multiple servers that you can run without running into problems where one server has a certain session, and the other server doesn’t.
Most modern web applications use JWT for authentication reasons like scalability and mobile device authentication.
Benefits of Using JWT
Compact: Being small in size, JWTs are perfect for being passed around in URLs, HTTP headers, or inside cookies.
Self-contained: JWTs carry all the information needed for authentication, which means no need to query the database multiple times.
Scalable: Ideal for modern web applications where scalability is crucial.
Secure: JWTs are digitally signed using either a secret (HMAC) or a public/private key pair (RSA or ECDSA) which safeguards them from being modified by the client or an attacker.
Stored only on the client: You generate JWTs on the server and send them to the client. The client then submits the JWT with every request. This saves database space.
Efficient / Stateless: It’s quick to verify a JWT since it doesn’t require a database lookup. This is especially useful in large distributed systems.
Drawbacks of JWT
No Token Revocation: Once a JWT is issued, it cannot be revoked easily until it expires.
Payload Size: As the payload increases, the size of the token also increases, which can affect performance.
Non-revocable: Due to their self-contained nature and stateless verification process, it can be difficult to revoke a JWT before it expires naturally. Therefore, actions like banning a user immediately cannot be implemented easily. That being said, there is a way to maintain JWT deny / black list, and through that, we can revoke them immediately.
Dependent on one secret key: The creation of a JWT depends on one secret key. If that key is compromised, the attacker can fabricate their own JWT which the API layer will accept. This in turn implies that if the secret key is compromised, the attacker can spoof any user’s identity. We can reduce this risk by changing the secret key from time to time.
To summarize, a JWT is most useful for large-scale apps that don’t require actions like immediately banning of a user.
Common issues during development
JWT Rejected
This error implies that the verification process of a JWT failed. This could happen because:
The JWT has expired already
The signature didn’t match - this implies that either the signing keys have changed, or that the JSON body has been manipulated.
Other claims do not check out. For example, in the case of the Google JWT example above, if the JWT was generated for App1, but was sent to App2, App2 would reject it (since the
aud
claim would point to App1’s ID).
JWT token doesn’t support the required scope
The claims in a JWT can represent the scopes or permissions that a user has granted. For example, the end-user may only have agreed that the application can read their data, but not modify it. However, the application may be expecting that the user agrees to modify the data as well. In this case, the scope required by the app is not what’s in the JWT.
JWT Decode failed
This error can arise if the JWT is malformed. For example, the client may be expecting the JWT is base64 encoded, but the auth server did not base64 encode it.
Benefits of Using JWT Tokens
JWT (JSON Web Token) offers several advantages over traditional authentication methods:
Stateless Authentication: Servers don’t need to store session information, improving scalability. It’s like giving each user a VIP pass that they can show at the door, without you needing to keep a guest list.
Compact and Self-Contained: All necessary information is in the token, reducing database queries. Think of it as a mini-file about the user that travels with them.
Cross-Domain / CORS Friendly: JWTs work well in distributed systems and enable single sign-on. It’s like having a universal key that works in multiple buildings.
Flexibility: JWTs can carry additional user data, reducing the need for extra API calls. Need to know if a user is a premium member? Pop it in the token!
Performance: By reducing database lookups, JWTs can significantly speed up your application. It’s like switching from a regular hard drive to an SSD.
Mobile-Friendly: JWTs work great for offline-first applications, allowing for smoother user experiences in mobile environments.
However, it’s important to note that JWTs also come with some considerations:
Token Size: Including too much information can make tokens large, potentially impacting performance. The RFC specification recommends keeping JWT payloads under 4kb to avoid browser limits and performance issues.
Token Management: Once issued, tokens can’t be easily revoked before expiration. Using short expiration times is crucial, as revoking tokens would go against the stateless nature of JWTs. If needed, you can implement a token blacklist, but this would introduce some server-side state, which is a trade-off against the main benefits of JWTs.
Key Vulnerabilities:
Weak signature algorithms: Always use strong algorithms like RS256.
Information disclosure: Never store sensitive data in the payload.
XSS attacks: Avoid storing tokens in localStorage; use HttpOnly cookies instead.
Token replay: Use short expiration times and implement token rotation.
Insufficient validation: Always validate the token signature and all relevant claims on the server side.
It’s also important to note that if the server’s private key is compromised, an attacker could generate their own valid session tokens, undermining the security of the entire system. Proper key management and rotation practices are crucial to mitigate this risk.
Best Practices:
Implement proper key management and rotation.
Keep your JWT libraries up-to-date.
Challenges and Considerations
While JWTs offer many benefits, they also come with challenges:
Token Expiration and Refresh:
Setting the appropriate expiration time for your JWT tokens is crucial for security. If the tokens are issued with an overly long lifetime, the risk of the token being compromised and misused increases. However, if the tokens have too short of a lifespan, it can negatively impact the user experience by requiring more frequent token refreshes.
To strike the right balance, many developers implement a hybrid approach, pairing a short-lived access token (which is typically a JWT) with a longer-lived refresh token. The access token is used for most API requests, providing the performance benefits of a stateless JWT. When the access token expires, the client can use the refresh token to obtain a new access token without requiring the user to re-authenticate.
This refresh token mechanism enhances user experience by minimizing the need for re-authentication, while still limiting the exposure window if an access token is compromised. The refresh token itself is usually a more opaque, stateful token stored securely on the server side.
Security Best Practices:
Always use HTTPS to prevent token interception.
Don’t store sensitive information in the payload.
Storage on the Client Side:
Where you keep the token matters. Local storage might be convenient, but it’s vulnerable to XSS attacks. Consider more secure alternatives like HttpOnly cookies.
Bearer Tokens
What is a Bearer Token?
A Bearer Token is a security token. With a Bearer token, the party in possession of the token (the “bearer”) is given access to the resource without further identification. Essentially, “If you have it, you can use it.”
How Does a Bearer Token Work?
Bearer tokens are typically generated by an authentication server and passed to the client. The client then includes the token in the HTTP Authorization header when making requests to access protected resources.
Unlike JWTs, Bearer tokens do not have a standardized structure. They are opaque to clients, meaning that clients should not attempt to decode or interpret them.
Benefits of Using Bearer Tokens
Simplicity: Easy to implement and use.
Flexibility: Can be used with various authentication mechanisms.
Secure: Since the token is opaque, clients cannot tamper with its content.
Drawbacks of Bearer Tokens
Stateless: Without additional infrastructure, tokens cannot be revoked.
Lack of Standardization: Bearer tokens do not have a standardized format, which can lead to inconsistency.
JSON Web Token can be stolen.
JWT vs. Bearer Token: Key Differences
Structure and Information
JWT: Structured with three parts (header, payload, signature) and carries information within the token itself.
Bearer Token: Opaque and doesn’t contain information about the user or claims.
Usability
JWT: Can be used for both authentication and information exchange. Ideal for stateless sessions.
Bearer Token: Primarily used for authentication. Suitable for simpler use cases where token revocation is not a concern.
Security
JWT: Offers strong security with its signature, but once issued, it cannot be revoked easily.
Bearer Token: Simpler but requires additional mechanisms for revocation and management.
When to Use JWT vs. Bearer Token
Deciding whether to use a JWT or Bearer token depends on your specific use case:
Use JWT if:
You need a self-contained token that can carry information between parties.
You require a token that is compact and can be passed around easily.
You need a token that can be verified without querying a database.
Use Bearer Token if:
You need a simple authentication mechanism.
You prefer opaque tokens for security reasons.
You have infrastructure in place to manage token revocation.
Best Practices for Using Tokens
Regardless of whether you choose JWT or Bearer tokens, here are some best practices to follow:
Secure Transmission: Always use HTTPS to ensure tokens are transmitted securely.
Token Expiry: Implement token expiration to reduce the risk of token theft.
Revocation: Develop a strategy for token revocation, especially if using long-lived tokens.
Storage: Store tokens securely. Avoid local storage if possible; consider using HTTP-only cookies.