Json Web Tokens - Should I use it?

10 minute read

Recently you might have read or heard a lot of different things about Json Web Tokens. When you start a project today, it may be difficult to know when you should or should not use JWT.

The idea of this article is to give you a better idea of Json Web Tokens and if they are suitable for your project. First of all, we will describe what JWT are. If you are already JWT initiated, feel free to go direclty to the next part. Then we will detail some security concerns you have to keep in mind when working with JWT, and finally some more generic elements about how to and where to use them.

JWT is…

As detailed in RFC 7519, Json Web Token (JWT) is

a compact, URL-safe means of representing claims to be transferred between two parties.

So, unlike what you can read in some posts, JWT is not an authentication protocol but a formating standard. You can use JWT in your authentication system but this is only one of the use cases of JWT.

JWT can be cryptographically signed (JWS) or encrypted (JWE). In each case, the token is made of a list of base64-url-encoded objects separated by a ’.’.

The general format is:

JWS:

Signed JWT format

JWE:

Encrypted JWT format

Because signed JWT are by far the most used (the encryption is optional), we will focus on it. So please keep in mind that the JWT we’ll be talking about are only encoded and not encrypted, which means that anyone is able to read it.

The header is a JSON object indicating the object type(which is always JWT) and the algorithm used to sign it.

A typical header would be:

{
    "typ":"JWT", 
    "alg":"HS256"  // HMAC SHA 256
}

From the RFC:

Of the signature and MAC algorithms specified in JSON Web Algorithms [JWA], only HMAC SHA-256 (“HS256”) and “none” MUST be implemented by conforming JWT implementations. It is RECOMMENDED that implementations also support RSASSA-PKCS1-v1_5 with the SHA-256 hash algorithm (“RS256”) and ECDSA using the P-256 curve and the SHA-256 hash algorithm (“ES256”). Support for other algorithms and key sizes is OPTIONAL.

Payload

The payload is a JSON object containing the claims, i.e. the informations to share. All claims names are preferably 3 characters long as the JWT is meant to be compact.

There are three types of claims:

  • Registered: claims used by the specifications. They are not mandatory but recommended. Most of the implementations recognize them all, but the most important ones are: iss(Issuer), exp(Expiration Date), jti(Token unique id), aud(Audience)

  • Public: claims defined in the IANA JSON Web Token Registry in order to prevent collision.

  • Private: claims not defined in the two others categories. They are subject to collision and so, must be used with caution.

A typical payload for authentication would be:

{
    "iss":"api",  // registered
    "aud":"front", // registered
    "jti":"1ezD2dfdfs342", // registered
    "name":"foo bar", // public
    "file":"myPicture.jpg" // private
}

Signature

This is simply the base64-url-encoded result of the following formula:

algInHeader(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)

In the case of the ‘none’ header alg value, the signature is only a blank string.

Example

The JWT using HMAC-256 for sharing the payload described above using the key value “secret” would be:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

.eyJpc3MiOiJhcGkiLCJhdWQiOiJmcm9udCIsImp0aSI6IjFlekQyZGZkZnMzNDIiLCJuYW1lIjoiZm9vIGJhciIsImFkbWluIjp0cnVlfQ.6-ucyvZMjUINhZbepdkQNap1GB7B5EH7I8N72z9PYkk

If using the none algorithm:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0

.eyJpc3MiOiJhcGkiLCJhdWQiOiJmcm9udCIsImp0aSI6IjFlekQyZGZkZnMzNDIiLCJuYW1lIjoiZm9vIGJhciIsImFkbWluIjp0cnVlfQ.

Security concerns

The JWT RFC itself is responsible for some security concerns.

The Unsecured JWT

An unsecured JWT is a JWT using the algorithm none. This kind of token must be implemented (as descibed in section 8 of the RFC 7519) and its purpose is to allow sharing information in a context where payload content are secured in another way. The drawback is, if we strictly follow the RFC, that a token indicating the ‘none’ header field alg value and no signature part is always considered as valid.

Considering that, if you use JWT for authentication purpose, a man in the middle can very easily generate a valid token and so, pass your authentication and access protected content.

If you want to test, you can use this online editor: https://jwt.io/

In order to prevent this kind of attack you just have to always check the presence of the signature if you intend to use a key in your JWT verification process.

Surprisingly, the JWA RFC 7518, says:

Implementations that support Unsecured JWSs MUST NOT accept such objects as valid unless the application specifies that it is acceptable for a specific object to not be integrity protected. Implementations MUST NOT accept Unsecured JWSs by default.

Somehow, that’s the opposite of the JWT RFC spirit and it could be the reason why the libraries mainly doesn’t implement this fix and simply consider an unsecured JWS as valid.

Using asymetric algorithm

The JWT RFC doesn’t force you to specify the intended algorithm during the validation check. In the case of choosing an asymetric algorithm (like RSA), tokens are signed using a private key and verified with the corresponding public key.

It’s therefore possible for an attacker to generate a token and then sign it using your public key like if it was the secret in a symetric alogrithm. During the token verification process, if you strictly follow the RFC, you will verify the token based on the symetric algorithm specified in the header, and then validate it.

Here is a pseudo code illustration of this:

// Real sender generates and send a token
token = {"typ":"JWT", "alg":"RS256"}.{"iss": "sender", "data": "myData"}
token += signAsym(token, privKey)

sendTo(token);

// Fake sender generates and send a token
fakeToken = {"typ":"JWT", "alg":"HS256"}.{"iss": "sender", "data": "falseData"}
fakeToken += signHMAC256(token, pubKey)

sendTo(fakeToken);

// Receiver: validates received tokens 
verify(token, pubkey);  // returns true
verify(fakeToken, pubKey); // returns true

A way to prevent this is to specify the expected algorithm that will generate the signature to your verification method, and that’s what most of the JWT libraries are now doing.

verify(token, alg, key);

Using Eliptic Curve algorithm

This security concerns is related to the version of the ECDH-ES algorithm indicated as being used in the JOSE RFC (which englobs JWT, JWE, JWS).

For further information, you can refer to the original paper and find a better explanation of the implication for the JWT

Using JWT

The typical use case, according to the RFC is to set the HTTP Authorization header. A resulting header could be:

Authorization: Bearer <JWT>

Because the domain is never used, you won’t have to take care of CORS (Cross-Origin Resource Sharing).

However, the front-end has to explicity provide this header in all its requests, which could be binding. A solution could be to use cookies instead but this also leads to other issues like the size of the JWT.

The next thing to take into account when using JWT is the storage in the front-end side. Both HTML5 localstorage and HTTP cookies can be used.

Storing the token

Using the localstorage

This is the most pratical way if you don’t need to support any a legacy browser. Using localstorage will allow you to use complex JWT (that’s to say JWT with many claims) as the size won’t be a problem. However, you will give access to your JWT and so be exposed to cross-site scripting (XSS) attacks.

Using a cookie could be more secure because you will be able to make them only available for HTTPS and/or only modified by HTTP protocol. In fact, it’s a good practice to use them in secure mode and HTTP only. Your front-end will not be able to read it but it doesn’t need to and you will be protected from an attack “man in the middle”.

However, you will still be vulnerable to cross-site request forgery (CSRF) attacks and will need to protect your application in another way (generally a specific header with CSRF purpose token). Another drawback can be the fact that cookies relate to domain and it can matter if you planned to build an open API for example.

Finally, using a cookie will limit the number of claims that you can use because cookies are restricted to 4 kBytes. It is widely enough for an autentification purpose JWT but can be restrictive for sharing the user information between the back-end and the front-end.

Sessions and JWT

The more people started using JWT in their authentication protocol, the more they began to use it for session management. This usage had made a lot of noise arround the web and many articles were written pointing out that this is a bad practice.

When using JWT, you might consider adding more information in your token. You are already using it, so why bothering to set up session management if you could just add session elements in the token? This will reduce the amount of data you store server side, and allow you to deal with session and authentication at once but:

  • This will increase the token size
  • You can’t (easily) invalidate the token
  • You need to safely store the token on the client side to avoid XSS or XSRF

By dealing with those points, you will very likely end up recreating a “standard way” of managing sessions.

A JWT is not editable. If some claims information are invalidated in some way (user’s profile change for example) and must be updated, your only solution is to generate a new token. This can lead to security concerns if you use JWT for session because you will have to store the list of invalidated tokens, otherwise the user could still access protected data by using an older token. It is still possible to ask the user to destroy his cookie/localStorage entry, but you can’t destroy the token itself. As an example, if you store user rights in their token and decide to remove its rights to one particular user, if he had previously stored a token and reuses it, its rights will stay unchanged, because the token is still “valid”.

JWT, Oauth2 and OpenID Connect

As we stated before, JWT is not an authentication protocol but a complementary tool to use in one, and it is now widely integrated in OAuth2.0 or OpenID Connect implementations.

RFC7523 describes the use of JWT as a Bearer token to get your access token. You migth want to use JWT as an Access token, but keep in mind what we explained before: you cannot easily revoke it and RFC7009 says that

Implementations SHOULD support the revocation of access tokens

JWT is used in OpenID Connect protocol for ID token. An ID token is passed to the client as the result of its authentication and contains information about the user, allowing its identification (it is sometimes refered as the user identity card). It states that the user have been authenticated and how.

One time actions

Sometimes in our systems, we introduce tokens to prevend redoing one specific action (password reset, file download link, …), and we end up storing those tokens somewhere waiting for them to be used. With JWT, we can think of new mechanisms allowing us to avoid storing tokens. A JWT will be generated for that specific action and be valid the time needed for this action to be performed.

For instance, during registration process we can easily make good use of it. Instead of storing in the database an account that might never be validated, we can store it in a JWT contained in the validation link of the confirmation email. This way, nothing is created server side untill the very end of the process.

Take away

The JWT is a very good, secure and lightweight tool to use in your web application authentication protocol. It’s also a good solution to deal with workflow that requires One Time Password like user’s password reset or triggering actions in another part of your application (i.e. another service).

However it’s not designed to replace session management mainly because it cannot be edited or invalidated.

In order to use it safely, you have to check that the implementation requires you to specify the encryption algorithm during the validation process. If you intend to use the none algorithm, remember that all unexpired JWT will be valid, as they won’t be signed.


Written by

Simon Henry

Lead developper, DevOps fan

Aurélien Clavelin

Full stack lead developer, tropical fish enthusiast