navigation
Security of JSON Web Tokens (JWT)

Introduction

JSON Web Tokens (JWT) mechanisms for user authentication become more and more popular in the applications. JWT gained particular popularity with the growing famousness of the microservice architecture: it entrusts the processing authentication data to the microservices, and therefore allows to avoid various authorisation errors, increase productivity and improve application scalability.

However, improper use of JWT can adversely affect application security. We will give examples of using JWT, analyse common errors in implementing authentication schemes using JWT, consider the main types of attacks on these schemes, and give recommendations on how to prevent them.

JWT format

This section covers the notion of JSON Web Tokens, what they consist of, how they are exploited for user authentication and what the advantages are of JWT compared with a classic authentication scheme using sessions.

JWT structure

In accordance with RFC-7519, JSON Web Tokens (JWT) are one of the ways to display data for its transfer between two or more parties as a JSON object.

As a rule, JWT consists of three parts:

  • Header
  • Payload
  • Signature

There are exceptions when JWT lacks a signature. This case will be reviewed later.

Each of the parts — header and payload — is an ordinary JSON object that needs to be additionally encoded using base64url algorithm. Afterwards, the encoded parts are connected with each other and, based on this, a signature is detected that also becomes a part of the token.

Generally, a token looks as follows:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6InVzZXIifQ.ZvkYYnyM929FM4NW9_hSis7_x3_9rymsDAx9yuOcc1I

You can see that the token has three parts divided by dots (picture 1).

JSON Web Token (example from jwt.io) Picture 1. JSON Web Token (example from jwt.io)

Red text is the header:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Initially:

{
"typ": "JWT",
"alg": "HS256"
}

The purple part is the payload:

eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6InVzZXIifQ

Initially:

{
  "id": "1337",
  "username": "bizone",
  "iat": 1594209600,
  "role": "user"
}

The blue part is the signature:

ZvkYYnyM929FM4NW9_hSis7_x3_9rymsDAx9yuOcc1I

Let us examine the details.

Header

header is a service part of the token. It helps the application to define how to process the received token.

This part is a JSON object and has the following format:

{
 "typ": "JWT",
  "alg": "HS256"
}

Here are the following fields:

  • typ — a token type, for example, JWT;
  • alg — the algorithm used to generate the signature.

The value of the filed “typ” is often ignored by applications, however the standards recommend to take it into account to provide backward compatibility.

“alg” field is obligatory. In this case HS256 (HMAC-SHA256) algorithm has been used in which a single secret key is used to generate and verify the signature.

For JWT signature symmetric encryption/signature algorithms can be used, e.g. RS256 (RSA-SHA256). The standard allows using other algorithms, including HS512, RS512, ES256, ES512, none, etc.

“none” algorithm shows that the token has not been signed. In this token there is no part containing a signature and it is impossible to define the authenticity of such a token.

Payload

Payload carries any information that helps an application to somehow identify the user. Certain service fields can be transferred additionally but none of them are obligatory, and we will not dwell on them.

In our case the payload contains the following JSON object:

{
  "id": "1337",
  "username": "bizone",
  "iat": 1594209600,
  "role": "user"
}

In this payload there are the following fields:

  • id — unique user identifier;
  • username — name of the user;
  • iat — a service field, time of token generation in Unix time format;
  • role — role of the user. It may contain “admin”, “user”, “guest”, etc.

As the fields in the payload part can be random, the application can store almost any data in this part. For example, the payload can store the user’s full name so as not to request this data every time from the database, and to speed up the application.

Signature

The signature is generated as follows:

The signature and payload parts are encrypted by base64url algorithm and afterwards united in a single box using a dot (“.”) as a divider.

Then the signature is generated by HMAC-SHA256 algorithm (in our example) and the signature is added to the initial box after a dot.

On a pseudocode this algorithm looks as follows:

signature = HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  SECRET_KEY
)

JWT = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + base64UrlEncode(signature)

The application after receiving JWT from the user calculates the value of the signature and compares it with the value that has been transferred in the token. If these values do not match, it means the token has been modified or generated by an untrusted party and the application will neither accept nor trust such a token.

The signature of the token in the example can be verified using “test” secret key, for example, on jwt.io

Authentication using JWT

Authentication using JWT is quite simple.

A user inserts his\her login data in the application or a trusted authentication service. In case of successful authentication, the service grants a token to the user containing information about this user (unique identifier, full name, role, etc.).

When further addressing the application, the token is transferred in the user’s requests (in cookies, request headers, post- or get-parameters, etc.).

After receiving the token, the application verifies its signature. Making sure the signature is valid, the application extracts the user’s data out of the payload part and authorises the user.

JWT advantages

What are the advantages of using JWT compared with a classic authentication scheme using sessions?

First, token use makes it possible not to store information about all the issued tokens. When a user addresses the application, he\she transfers their token. The application verifies the signature and extracts necessary fields out of the payload.

Second, the application does not have to issue and verify tokens by itself, as a separate authentication service is often used for these purposes.

Third, in case of a separate authentication service it is possible to organise a single-entry point to various services with the same login data. After going through the authentication procedure once, the user with the token will be able to get access to those resources that trust this authentication service.

Finally, the application can store almost any data in the payload part that can substantially increase the application efficiency, if its architecture is correct.

The factors above resulted in JWT authentication scheme being widely used in various corporate applications. Such a scheme is especially popular in applications using microservice architecture. With this approach, each service gets necessary user data directly from the token and does not waste time on acquiring this information from the database.

Attacks on JWT

This section will review the main attacks on JWT and recommendations will be given on how to prevent such attacks.

Token capture

User’s token capture may lead to several negative consequences.

First, as JWT is transferred openly, it is enough to apply base64UrlDecode function to the payload part to receive the initial data stored there. Obviously, a criminal having captured the token, will be able to extract the user’s data stored in the token.

In order to avoid such a threat, the best practice is to:

  • Use a secure connection during token transfer;
  • Never transfer user’s sensitive data in tokens, limiting oneself to impersonal identifiers.

Second, the criminal having captured a token will be able to reuse this token and access the application on behalf of the user whose JWT has been captured.

The recommendations here will be as follows:

  • Like in the first case, to use secure connection during token transfer;
  • To limit the JWT lifetime and use refresh tokens.

Refresh tokens

In modern authentication schemes based on JWT, the user receives two tokens after authentication:

  • access token — JWT based on which the application identifies and authorises the user;
  • refresh token — a random token to renew access token.

Access token in this case has a limited lifespan (e.g., 1 minute). Refresh token has a longer lifespan (day, week, month) but it is one-off and serves only to renew the user’s access token.

The authentication scheme in this case looks as follows:

  • the user goes through identification and receives access token and refresh token from the server;
  • when addressing the resource, the user transfers his access token in the request, based on which the server identifies and authorises the client;
  • if the user’s access token expires, the user transfers his refresh token in the request and gets new access token and refresh token from the server;
  • if the user’s refresh token expires too, the user must go through the identification process again.

Mining the key for signature symmetric algorithm

In case of symmetric algorithm for signing JWT (HS256, HS512, etc.) a criminal can try to match the key phrase.

Having done so, the criminal can manipulate the JWT tokens like the application does and therefore can get access to the system on behalf of any registered user.

In our example (see part 1 of the article) a “test” box was used as the key phrase to sign JWT. This key phrase is simple and short and can be found in all the main dictionaries for passwords mining. A criminal can easily match the key phrase using John the Ripper or hashcat.

In this case the recommendations are as follows:

  • to use and store the key phrases as confidential information, having considerable length, consisting of upper- and lower-case Latin letters, numbers and special symbols;
  • to provide periodic change of the key phrase. This will be less convenient for the users, as they will have to go through identification again, but will help to avoid compromising the key information.

Using “none” algorithm

As we have already mentioned in the first part of the article, use of “none” algorithm in JWT header shows that the token has not been signed. Such a token lacks a part with the signature, and it is impossible to verify authenticity of this token.

Let us review a similar attack in our example. Our non-coded token looks as follows:

header:
{
 "typ": "JWT",
  "alg": "HS256"
}
payload:
{
  "id": "1337",
  "username": "bizone",
  "iat": 1594209600,
  "role": "user"
}
signature:
ZvkYYnyM929FM4NW9_hSis7_x3_9rymsDAx9yuOcc1I

Suppose, we want the application to regard us as an administrator. We need, therefore, to change the field “role” in payload for “admin”. But if we introduce these changes in the token, its signature will become invalid and the application will not accept such JWT.

To bypass this security mechanism, we can try to change the field “alg” in the token header for “none”. Our token will look as follows:

header:
{
 "typ": "JWT",
  "alg": "none"
}
payload:
{
  "id": "1337",
  "username": "bizone",
  "iat": 1594209600,
  "role": "admin"
}

As we are using “none” algorithm, there is no signature in this case. Our encoded JWT will look as follows:

eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0

This token will be sent to the server. A vulnerable application, after checking the JWT header and detecting “alg”: “none”, will accept this token without any verification as if it were legitimate, and as a result we will gain administrator rights.

As methods of precaution against such attacks:

  • it is necessary to keep a white list of authorised algorithms on the application side and to dismiss all tokens having a signature algorithm that is different from the one authorised on the server;
  • it is recommended to work with one algorithm only, e.g., HS256 or RS256.

Changing the signature algorithm

In case of using asymmetric algorithms for token signature, the signature shall be performed using a private service key and signature verification — using a public service key.

Some libraries used for working with JWT contain logical errors — when receiving a token signed with a symmetric algorithm (e.g., HS256) a public service key will be used as a key phrase for verifying the signature. As a public service key is not secret data, a criminal can easily get it and use for signing own tokens.

To review this example, we will require a new JWT:

header:
{
  "alg": "RS256",
  "typ": "JWT"
}
payload:
{
  "id": "1337",
  "username": "bizone",
  "iat": 1594209600,
  "role": "user"
}
signature:
YLOVSKef-paSnnM8P2JLaU2FiS8TbhYqjewLmgRJfCj1Q6rVehAHQ-lABnKoRjlEmHZX-rufHEocDxGUYiGMjMexUQ3zt-WqZITvozJ4pkvbV-mJ1nKj64NmqaR9ZkBWtmF-PHJX50eYjgo9rzLKbVOKYOUa5rDkJPHP3U0aaBXFP39zsGdOTuELv436WXypIZBeRq2yA_mDH13TvzegWCK5sjD4Gh177bCq57tBYjhGIQrDypVe4cWBPlvwFlmG8tdpWGu0uFp0GcbTAfLUlbTSuGROj88BY0XeUs0iqmGlEICES3uqNx7vEmdT5k_AmL436SLedE0VHcyxve5ypQ

When encoded, it will look as follows:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6InVzZXIifQ.YLOVSKef-paSnnM8P2JLaU2FiS8TbhYqjewLmgRJfCj1Q6rVehAHQ-lABnKoRjlEmHZX-rufHEocDxGUYiGMjMexUQ3zt-WqZITvozJ4pkvbV-mJ1nKj64NmqaR9ZkBWtmF-PHJX50eYjgo9rzLKbVOKYOUa5rDkJPHP3U0aaBXFP39zsGdOTuELv436WXypIZBeRq2yA_mDH13TvzegWCK5sjD4Gh177bCq57tBYjhGIQrDypVe4cWBPlvwFlmG8tdpWGu0uFp0GcbTAfLUlbTSuGROj88BY0XeUs0iqmGlEICES3uqNx7vEmdT5k_AmL436SLedE0VHcyxve5ypQ

As in this case we use RS256 algorithm for signature, we will require both public and private keys.

Public key:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----

Private key:

-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw
kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr
m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi
NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV
3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2
QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs
kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go
amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM
+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9
D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC
0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y
lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+
hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp
bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X
+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B
BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC
2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx
QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz
5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9
Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0
NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j
8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma
3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K
y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB
jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=
-----END RSA PRIVATE KEY-----

For tests we will utilise jwt.io (picture 2).

Initial JWT Picture 2. Initial JWT

As in previous example, we modify the token:

header:
{
 "typ": "JWT",
  "alg": "HS256"
}
payload:
{
  "id": "1337",
  "username": "bizone",
  "iat": 1594209600,
  "role": "admin"
}

When encoded, the header and payload look as follows:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0

We only have to read the signature using a public service key.

To begin, let us transfer the key to hex-representation (picture 3)

hex-representation of the key Picture 3. hex-representation of the key

Then we generate a signature using openssl (picture 4)

Generating signature for JWT Picture 4. Generating signature for JWT

We add the value E1R1nWNsO-H7h5WoYCBnm6c1zZy-0hu2VwpWGMVPK2g to an already existing box, and our token looks as follows:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0.E1R1nWNsO-H7h5WoYCBnm6c1zZy-0hu2VwpWGMVPK2g

We insert our public key into “secret” on jwt.io, and, as we can see, JWT goes through verification successfully (remember to check the box “secret base64 encoded”!) (picture 5)

Successful JWT signature verification Picture 5. Successful JWT signature verification

To prevent this attack, we recommend:

  • to work with one algorithm only, e.g. HS256 or RS256;
  • to select well-known and reliable libraries for working with JWT that are less likely to contain logical errors in token verification procedures.

Key identifiers manipulation

RFC-7515 standard describes “kid” header parameter (Key ID, key identifier). This standard also states that the format of this field is not strictly defined, so the developers can interpret it to their convenience, and this often leads to various mistakes.

Let’s take the following JWT header as an example:

{
 "alg" : "HS256",
 "typ" : "JWT",
 "kid" : "1337"
}

We suppose that for token verification a key with 1337 identifier from the database will be used here. In case of encoding errors this field can be vulnerable to SQL injections:

{
 "alg" : "HS256",
 "typ" : "JWT",
 "kid" : "1337' union select 'SECRET_KEY' -- 1"
}

In this case “SECRET_KEY” box will be used as the key phrase instead of a potential key from the database to verify the key signature.

In the next example we suppose that a key from “keys/service3.key” file will be used to verify the token.

{
 "alg" : "HS256",
 "typ" : "JWT",
 "kid" : "keys/service3.key" 
}

There is a possibility that in case a parameter is not validated, a criminal can perform Path Traversal (Directory Traversal) attack, and instead of a potential route to the file he can transfer a route to a public file to the “kid” field:

{
 "alg" : "HS256",
 "typ" : "JWT",
 "kid" : "../../../images/public/cat.png" 
}

The criminal can access “cat.png” file and sign JWT using the contents of this file, as this file is public (e.g., published on one of the service pages). The service, having received a route in “kid” field to “cat.png” file uses its contents as a key file to verify the token signature (that will be successful as the criminal has taken care of that beforehand).

A recommendation to prevent such attacks is simple:

  • it is necessary to always validate and sanitise the data received from the user even if it has been received as JWT.

Conclusion

JSON Web Tokens are very popular and are highly regarded for their convenience. If used correctly, JWT can prevent errors of inadequate authorisation, allow simple and easy distribution of information flows between the servers, organise a single entry-point for various services with the same login data and even increase the service efficiency.

However, if misused, this technology may put entire systems at risk, which may even result in an all-out compromise of the login credentials for all system users.

To conclude, in order to make the JWT use safe and secure, it is recommended to:

  • Use secure connection when transferring tokens;
  • Never transfer users’ sensitive data in the tokens;
  • Limit JWT lifespan and use “refresh tokens” mechanism;
  • Use long key phrases;
  • Keep a white list of authorised signature algorithms on the application side;
  • Work, ideally, with one signature algorithm only;
  • Choose well-known and reliable libraries for JWT operation;
  • Always validate and sanitise the data received from users.