DEV Community

Cover image for How JWTs Could Be Dangerous and Its Alternatives
Pragati Verma
Pragati Verma

Posted on

How JWTs Could Be Dangerous and Its Alternatives

Introduction

JSON Web Tokens (JWTs) are the most popularly used tokens for web authentication and managing user sessions in modern-day software applications. There is loads of information on the benefits or advantages that JWTs bring to the table, however, very few developers and software architects are familiar with the potential dangers and inefficiencies of using JWT tokens.

In this article, we’ll discuss how JWTs can make websites vulnerable to a variety of high-security threats and attacks if not managed properly. And because JWTs are extensively used in authentication, session management, and access control techniques, these flaws might jeopardize the entire website and its users.

Before we dive into the details, let’s have an overview of JWTs and how they almost become a standard for software developers.

What are JWTs?

JSON web tokens (JWTs) are a standardized format for securely transferring cryptographically signed JSON data across systems. They can potentially include any type of data but are most typically used to transfer data ("claims") about users as part of authentication, session management, and access control procedures.

Unlike traditional session tokens, all of the data required by the server is saved on the client side within the JWT. As a result, JWTs are a common solution for widely distributed websites where consumers must interact seamlessly with numerous back-end servers.

A JWT consists of the following three parts - header, payload, and signature, where the header and payload are base64url-encoded JSON objects which can be decoded from the token to reveal information. The header includes information about the token i.e. metadata such as the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA, whereas the payload contains the user's real "claims." There are no constraints on the payload's content, although it's crucial to note that a JWT is not encrypted.

Components of JWT

As a result, whatever information we put in the token is still viewable by anyone who intercepts it. Thus, the security of any JWT-based mechanism is heavily reliant on the cryptographic signature.

The signature, which is a Message Authentication Code (or MAC), is the last component of a JWT. A JWT signature can only be produced by someone who has both the payload (including the header) and a provided secret key.

Because the signature is obtained directly from the remainder of the token, altering a single byte of the header or payload results in an incorrect signature.

It should be impossible to produce the right signature for a given header or payload without knowing the server's secret signing key.

Each part is separated by a dot in the JWT as shown below:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Enter fullscreen mode Exit fullscreen mode

When decoded, the information can be revealed as follows:

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}
Enter fullscreen mode Exit fullscreen mode

Payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
Enter fullscreen mode Exit fullscreen mode

Signature:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),

your-256-bit-secret

)
Enter fullscreen mode Exit fullscreen mode

Decoding JWT

As we can see, the signature is really the key part of the JWT!

The signature is what allows a fully stateless server to be certain that an HTTP request belongs to a specific user simply by looking at a JWT token present in the request itself, rather than forcing the password to be sent each time the request is made.

For further user actions, the server merely validates the signed section, obtains user information, and allows the user to do the action. As a result, the DB call is fully avoided.

But there is one more thing you should know about JWT tokens. That is, it uses an expiry period to self-destruct. It is usually set to 5 to 30 minutes. And because it is self-contained, it is difficult to revoke/invalidate/update. This is truly the root of the issue. Let’s look into this in detail in the next section.

How JWTs Could be Dangerous?

Although JWT eliminates the database lookup, it brings security concerns and other complexity to the process. Security is binary—it is either secure or not. As a result, using JWT for user sessions is dangerous.

The biggest problem with JWTs is that the token will continue to work until it expires, and the server has no easy way to revoke it. This could be extremely dangerous in situations such as the following:

Logout doesn’t actually log you out of the system. The JWT token can continue to live for whatever duration is set apart for its expiration even after you have logged out, which means if someone gets access to that token during that time, they can continue to access it until it expires.

Similarly, you can’t block any user from the system for moderation or whatever reason because they will continue to have access to the server until the token expires.

Suppose the user was an administrator who was downgraded to an ordinary user with lower privileges. Again, this will not take effect immediately, and the user will remain an administrator until the token expires.

Because JWTs are frequently not encrypted, anyone who can execute a man-in-the-middle attack and sniff the JWT now has access to your authentication credentials. This is made easier because the MITM attack only has to be carried out on the server-client connection.

Moreover, many libraries that implement JWT have had many security issues. Also, many real-world programs require servers to save the user's IP address and track APIs for rate throttling and IP whitelisting. As a result, you'll need to employ a lightning-fast database anyhow. It's unrealistic to believe that using JWT will render your app stateless.

Alternatives

One typical solution is to keep a database of "revoked tokens" and verify it for each call. If the token is in that revoked list, then prevent the user from performing the next operation. But now you're making an additional call to the database to see if the token has been revoked, which defeats the point of JWT entirely.

The answer is not to avoid using JWT for session reasons entirely. Yet instead, do it the old-fashioned, but time-tested way. Make the database lookup so quick (sub-millisecond) that the extra call isn't necessary by using solutions such as Redis along with JWT such that we can avail the benefits of JWTs but remove most of the security threats discussed earlier.

In this scenario, if the JWT verification is successful, the server will still proceed to Redis and double-check the information there. However, if the JWT verification fails, there is no need to search the Redis database.

Another advantage of this technique is that you may utilize existing JWT libraries on both the front end and back end without developing your own custom way of storing data in Redis.

Conclusion

In this post, we learned what JWTs are and how they are used for authentication. JWTs are simply JSON payloads with an easily verifiable and unforgable signature. We also discussed the vulnerabilities that improper handling of JWTs can introduce and what are the alternatives to use.

That’s all for this article. In case you want to connect with me, follow the links below:

LinkedIn | GitHub | Twitter

Top comments (24)

Collapse
 
dagnelies profile image
Arnaud Dagnelies • Edited

I don't really understand the attention this triggers. If you want a session, just use a (http-only cookie), that's super simple, secure, etc. Cookie is set, you're logged in, cookie is cleared, you are logged out. It cannot be simpler and is the traditional way of managing sessions. It works perfectly.

That said, JWTs are great and super useful, just not as a replacement for user sessions! JWTs are basically most useful for other things: "authorization" to use some third party API on behalf of the user, internal communication between your own services and mostly in the OAuth2/OpenID flow. This basically just requests a JWT to fetch the user's profile email from the identity provider.

Last but not least, you can also put a JWT as http-only cookie to validate a user session without DB lookup. That way, it cannot be "stolen" and clearing the token is sufficient to "logout" the user.

Collapse
 
ayankumarsaha profile image
Ayan Kumar Saha

My experience with cookies is not so good. If the end user is using browsers like "brave" which by default blocks cookies or using some sort of cookie-blocking browser plugin then the whole cookie-based session management will not work properly.

Collapse
 
cosinus30 profile image
cosinus30

It seems default behaviour of brave is not to block all cookies, but to block 3rd party cookies. Unless user disables all cookies (which is not recommended by the brave team) http only cookies will work just fine.

Collapse
 
josethz00 profile image
José Thomaz

I agree with you, but if your client is a mobile app, you might have to think a little bit more, because cookies can be hard to handle in mobile apps

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Hi. There's a much simpler and efficient way to invalidate tokens. I guess I can make a little article explaining. Stay tuned. Since it is probably a small topic I'll most likely have it out by end of day.

Collapse
 
ankit9761 profile image
Ankit Kanyal

Wow great article thanks for sharing the info

Collapse
 
phlash profile image
Phil Ashby

Thank you for the detailed description of JWTs, and for noting the design consideration that they will continue to work for the expiry period (unless revoked in some way).

TL;DR - it depends, look at your wider business risks and choose technologies that minimize total risk.

As with all security issues - this is a risk balance, usually trading complexity/cost of distributed caches (eg: Redis) and database performance against the possible outcomes of a token being valid for <n> minutes. In federated business relationships (ie: those 'log in with Google' buttons), JWTs are often an essential part of the larger OpenID Connect protocol in use.

If (as in your example) your business is not federated, is critically dependant on rapid session revocation and performs database transactions on most requests, then retaining user authentication in the database makes sense. If your application doesn't make many database transactions, then it may be more cost efficient (at the risk of increasing complexity) to introduce a cache like Redis, to limit the performance needs of the database.

I would also ask 'would a problematic user be identified within the token expiry period', and 'whats the worst that could happen'? There may now be humans in the loop, eg: support teams discussing user reports, investigating posts, etc. and taking several minutes before a decision to block/ban a user is taken - likely longer than their current token expiry?

Finally, there are network mitigation measures that can be applied, such as rate limiting, or IP blocking, to limit impact.

Collapse
 
cosinus30 profile image
cosinus30

What I do not understand is how JWTs suffer from man in the middle attacks. If the data is sent through secure channels (HTTPS) man in the middle should not be able to retrieve the data from the encrypted data, right?

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

I promised, so I delivered. Here's a much simpler and more effective way of invalidating tokens.

Collapse
 
ahmetilboga2004 profile image
Ahmet İlboga

Thanksss🥳

Collapse
 
darjanbogdan profile image
Darjan Bogdan • Edited

JWT is just a format, a way to transfer *insensitive * data (thus not encrypted, but signed) that uniquely identifies a party (client, user) that issues request to the server. The security doesn't lie in the token itself, the security is ensured by complying to auth protocols (e.g. OAuth2, OpenID, ...) that are being used by both clients (gets a token) and servers (validates and uses).

Having said that, JWT indeed could impose a security risk since they have expiration (not self-destruction), and if some bad guy gets in hands valid/non-expired token it can effectively impersonate client and possibly user.

So, in cases when zero-trust client device is connecting to the server, JWT formatted tokens (and all other self-contained tokens) is not suitable, but so called "reference" tokens are issued by authorization/identity servers instead. Usually, validation of such tokens is implemented following OAuth 2.0 Token Introspection method described in RFC 7662. This effectively gives possibility to on-demand revoke any token in case of compromises.

In security world, personal devices which includes laptops, phones and desktops are considered safe enough to use JWT, thus you can see a wide spread adoption across the browsers and applications. In IoT world, you will almost never see JWT usage, due to inherently unsafe environment in which such devices usually work.

Collapse
 
kostyatretyak profile image
Костя Третяк • Edited

Sometimes a couple of tokens are used - refresh and access token. Access token has a lifetime of only a few minutes. Instead, the refresh token can have a lifetime of months. The additional advantage is that each of these tokens are works with different servers.

With such a scheme, the authentication/authorization server is accessed only with a login and password, or with a refresh token. And the resource server deals only with the access token.

If there is a need to remove access for a specific user, the maximum time during which he will still have access to prohibited resources will be equal to the lifetime of the access token (ie, a couple of minutes). After the access token expires, this user will be forced to contact the authorization server with a refresh token, but he will be denied access token renewal.

If in a specific case instant revocation of access rights is important, and even a few minutes will be critical, then the gain from JWT is really leveled off, and you will have to go to the database for each request. Apparently, such a scenario is quite rare.

Collapse
 
ecki profile image
Bernd

Assuming token revocations are seldom you can use a generational approach: you don’t care about expired tokens, but if you revoke a valid one you will have to sent it’s Id to a fast lookup (db or redis) and in addition update the generational threshold on all policy enforcement points (servers). This can be done with a cache invalidation publish token or a cache hoerwchy. „All tokens below Generation number x now need an additional lookup roundtrip“. Since all new tokens have a newer generation number (or minimum issue timestamp) those won’t be looked up in the cache until somebody inserts a new revocation. If you expect very seldom revocation you can instead broadcast the IDs or the revoked userId.

And just to mention some auth. systems combine JWT it’s callbacks to validate the key or request analysis data. For example in OIC. In that Case the callback can also check revocation.

BTW JOSE also supports encrypted tokens, if you use a authenticated encryption it can even double as a validation. However this would require a pairwise shared secret. In some cases where this is not wanted you would encrypt a signed token.

Collapse
 
szalonna profile image
Joe

My understanding is that the original idea for JWT was to create sg which can be used on a controlled environment, like between microservices. But as soon as the token moves out from this safe zone, you should consider using a standard session management on the public entry points and map the user requests to short lifespan tokens.

Some comments have been hidden by the post's author - find out more