Vulnerabilities in Authentication with JWT
Published: 2023-08-14 17:06:57

---
title: Vulnerabilities in Authentication with JWT
published: true
date: 2023-08-14 16:49:41 UTC
tags: 
canonical_url: https://blog.scalablebackend.com/vulnerabilities-in-authentication-with-jwt
---

![Keep out](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/swuuwrsix91hvi6reb9a.png)

After working with JWT more in-depth for the past few months, I realized most of the learning materials are of poor quality.

Today, I want to make it clear how JWT should be used in your authentication flow, what are its security vulnerabilities, and how to avoid them.

## What is a JWT

From its [introduction page](https://jwt.io/introduction), we learn the following:

> JSON Web Token (JWT) is an open standard ([RFC 7519](https://tools.ietf.org/html/rfc7519)) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object

{% embed https://jwt.io/ %}

### Content

In practice, a JWT is a string that looks like `xxxxx.yyyyy.zzzzz` , where the sections are respectively the header (`xxxxx` ), payload (`yyyyy` ), and signature (`zzzzz` ).

**Header**

The header is a JSON object, which typically defines the algorithm used, and the type of the token JWT. Its encoded in base64 to be used as a string.

**Payload**

The payload is also a JSON object, where you define the user information. its also encoded in base64.

**Signature**

The signature is generated based on the header and payload, using an algorithm (such as HMAC SHA256) and your secret.

### Result

In the end, you generate a JWT, which anyone can read the information of. But you are the only one able to verify a JWT has not been tampered with. Nobody can change a JWT payload and sign it with your own secret.

![Decoding JWT](https://cdn.hashnode.com/res/hashnode/image/upload/v1691953360897/90f585ac-1987-41f0-8400-6e8c0ff39c90.png)

## Processes

### Authentication

In the authentication process, a user typically sends his credentials to an API, which tries to find the corresponding account in a database.

If an account is found, a JWT is generated with the user information, such as id, name, or even his roles (admin? user?).

![Authentication](https://cdn.hashnode.com/res/hashnode/image/upload/v1691953472010/508bca2e-98a5-4cda-98af-92c80163cbed.webp)

### Authorization

Then, a user is able to request a private endpoint, where he needs to be authenticated. This is known as the authorization process:

![Authorization](https://cdn.hashnode.com/res/hashnode/image/upload/v1691953546396/526c1230-3a98-48f6-9c33-aedbfc684104.webp)

Because a JWT contains user information (its stateless/self-contained), the API doesnt need to request a database. This is amazing in terms of performance, and even better on distributed architecture.

This is the normal use case of a JWT, if youre making requests to your database during authorization, you defeat the purpose of JWT.

### Limitations

On one side, the self-contained aspect of JWT makes it amazing. On the other, because youre not requesting to your database, you cannot invalidate a JWT.

This is an issue for both functionality & security. If a user gets his token stolen, or if you have a role system and someone gets a role removed, he can still use a previous token when he shouldnt be allowed to (while the token is not expired).

This problem is solved by limiting the lifetime of tokens to a short duration, such as 5 minutes. But you dont want to ask a user credentials every five minutes.

Thats why you need to implement **refresh tokens.** Its often treated as beyond the scope of basic learning materials, but its mandatory.

{% embed https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/ %}


### Refresh tokens

Refresh tokens are completely different from the regular JWT you use for authorization (which are called identity tokens). They are long-lived tokens (~7 days) and can be used a single time to generate a new identity token.

If you respect those properties, you can implement identity tokens in different ways. You can have a table that stores refresh tokens and the corresponding user, which is updated every time its used.

For refresh tokens, I usually generate a JWT where the payload contains two properties, a `sub`, and `userId`. The sub contains a UUID, which is stored in a database, and map to its corresponding user.

When a user tries to log in based on a refresh token, I find the corresponding `sub` in my database and verify the `userId` it maps to is the right one (it avoids a potential situation where a user connects with a previous user refresh token UUID).

In the end, you should have two endpoints for login, one with user credentials, and one with a refresh token. Now, there are multiple ways to generate and store those tokens, which leads us to the next section: vulnerabilities.

## Security vulnerabilities

### XSS attack

There is one implementation issue Ive seen too many times, how to store a JWT. In most online examples, you can see a JWT being returned inside a request response (_body_), and stored in localStorage or simply in memory.

This is the source of a huge security vulnerability, **XSS attack**.

{% embed https://owasp.org/www-community/attacks/xss/ %}

An XSS attack is possible when a malicious third party manage to inject code inside your application.

From here, anything allowed by your runtime is possible. A third party could secretly read the content of `localStorage` and send it to their own server, stealing JWTs for example.

Storing your JWT in-memory isnt enough. A third party can easily intercept a request response, and read users JWTs from there. There is a single solution: storing them inside secured `cookies`.

A secure cookie is configured with, at least, the [Secure and HttpOnly](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) attributes. Its also a good practice to use [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#samesite_attribute) to avoid CSRF.

There is also some arguments in favor of storing refresh tokens inside localStorage instead of cookies. The impact of a refresh token being stolen [is reduced](https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/#You-Can-Store-Refresh-Token-In-Local-Storage) by its one-time only validity.

### CSRF

Cookies are much better than `localStorage` for our use case, but theyre not perfect. With cookies, you dont control when they are sent, your browser sends them with every request.

Its the source of CSRF, short for Cross Site Request Forgery.

{% embed https://owasp.org/www-community/attacks/csrf %}

From a general perspective, CSRF can happen when a third party trick a user into making a malicious request. The CSRF page from OWASP gives an [amazing scenario](https://owasp.org/www-community/attacks/csrf#get-scenario) with a bank transfer endpoint.

Using cookies with `SameSite` mitigates CSRF, but only a [CSRF token](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#token-based-mitigation) can completely get rid of it.

### Signing

There are two potential vulnerabilities when you sign a JWT token, bad algorithm and secret key.

JWT can be signed with different algorithms. The list can differ depending on [which library](https://jwt.io/libraries) you are using. Library authors are responsible to implement those.

The default algorithm is usually _HS256_, but using a bad implementation or wrong configuration might end up with you using the **none** algorithm.


{% embed https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/#Meet-the--None--Algorithm %}

What this algorithm does is nothing! It generates an empty signature, which allows any third party to modify the JWT payload, and your server will still believe the modified JWT is valid.

My advice is to use well-known/maintained libraries and not try to use the **none** algorithm. You can also verify the content of tokens you generate using [jwt.io](http://jwt.io).

{% embed https://auth0.com/blog/brute-forcing-hs256-is-possible-the-importance-of-using-strong-keys-to-sign-jwts/ %}

Previously, we talked about refresh tokens, but there is a vulnerability introduced by using different types of tokens.

If you configure your refresh tokens to be signed with the same secret as the identity tokens, a malicious user could send an identity token where you expect a refresh token and vice-versa.

Depending on your implementation, you might grant access to a malicious user where you shouldnt. There is a single solution: use a different secret for your identity & refresh tokens.

### Validation

During the validation phase, bad implementation could introduce security vulnerabilities.

{% embed https://www.npmjs.com/package/jsonwebtoken %}

There is an example with the NodeJS [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) library. The right implementation is to use the [verify method](https://www.npmjs.com/package/jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) to ensure the token is valid and decode it.

On the other hand, it provides a [decod method](https://www.npmjs.com/package/jsonwebtoken#jwtdecodetoken--options) that doesnt check if the token is valid. Some developers not used to working with JWT might use the second method instead, virtually accepting any JWT token.

Ensure you are using well-known libraries and learn them properly before implementing anything sensitive.

## Authors note

I advocate for the use of cookies over localStorage to mitigate XSS attacks, but thats not a reason to ignore potential XSS attacks altogether. I definitely recommend following good practices regarding XSS attacks.

For example, using JS eval [should be avoided](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#dangerous-contexts). You can also verify your dependencies using tools like [snyk.io](http://snyk.io), and only use trusted CDN.

{% embed https://auth0.com/ %}

Building your own authentication system is an arduous task, I would recommend anyone to either use an authentication provider such as [Auth0](https://auth0.com/), or have a dedicated team working full time on authentication.

* * *

Do you want to learn how to create a **backend** application, add a **secure authentication system** , and much more?

{% embed https://www.scalablebackend.com/ %}

* * *

Cover photo by [Edwin Hooper](https://unsplash.com/fr/@edwinhooper?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/fr/photos/TJ9rBJAAguQ?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)