DEV Community

Teddy MORIN
Teddy MORIN

Posted on • Originally published at blog.scalablebackend.com on

Vulnerabilities in Authentication with JWT

Keep out

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, we learn the following:

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object

JSON Web Tokens - jwt.io

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

favicon 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

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

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

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.

What Are Refresh Tokens and How to Use Them Securely

Learn about refresh tokens and how they help developers balance security and usability in their applications.

favicon auth0.com

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.

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 attributes. Its also a good practice to use SameSite 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 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.

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 with a bank transfer endpoint.

Using cookies with SameSite mitigates CSRF, but only a CSRF token 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 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.

Critical vulnerabilities in JSON Web Token libraries

Which libraries are vulnerable to attacks and how to prevent them.

favicon auth0.com

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.

Brute Forcing HS256 is Possible: The Importance of Using Strong Keys in Signing JWTs

Cracking a JWT signed with weak keys is possible via brute force attacks. Learn how Auth0 protects against such attacks and alternative J...

favicon auth0.com

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.

jsonwebtoken - npm

JSON Web Token implementation (symmetric and asymmetric). Latest version: 9.0.1, last published: a month ago. Start using jsonwebtoken in your project by running `npm i jsonwebtoken`. There are 24736 other projects in the npm registry using jsonwebtoken.

favicon npmjs.com

There is an example with the NodeJS jsonwebtoken library. The right implementation is to use the verify method to ensure the token is valid and decode it.

On the other hand, it provides a decod method 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. You can also verify your dependencies using tools like snyk.io, and only use trusted CDN.

Auth0: Secure access for everyone. But not just anyone.

Rapidly integrate authentication and authorization for web, mobile, and legacy applications so you can focus on your core business.

favicon auth0.com

Building your own authentication system is an arduous task, I would recommend anyone to either use an authentication provider such as Auth0, 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?

Enterprise Grade Back-End Development with NodeJS

Learn how to write reliable backend applications using Node.JS & NestJS!

favicon scalablebackend.com

Cover photo by Edwin Hooper on Unsplash

Top comments (1)

Collapse
 
emilmarian profile image
emil marian

Good article. Interesting stuff with cookies over localstorage, I will definitely check that.