JSON Web Tokens are formally standardized in RFC 7519 and commonly referred to as JWT. JWT is a means of confirming someones claim to use a system between two parties, specifically confirming that a client who wants to use your system is authorized to do so. Therefore, it is a common way of authorizing calls to microservices endpoints via a REST API. JWT, at first glance, may seem complicated but once you look under the hood it is quite straightforward. The aim of this post is to try and explain JWT in simple terms. I have found this very beneficial when implementing JWT for microservices authorization, it is always very import to understand the concept of what you are coding.

Overview

RFC 7519, in it’s Abstract, defines JWT as follows:

“JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.”

RFC 7519

A cursory read of this definition doesn’t really help understand what JWT actually is unless you spend quite some time parsing through the text and breaking it down. Instead, I think the following analogy helps clarify exactly what JWT is. Imagine that you are an automobile company. The services department of this automobile company has a policy of not keeping any records of the work that has been done on a car when it’s brought in. Instead, they give the customer a paper that states what work has been done. The way that the auto company knows that this paper truly represents the work that has been done is that it is signed by the technician who performed the job. This way they do not have to record anything and it is the responsibility of the customer to produce the signed paperwork. This analogy is not far off from how JWT works.

JWT is a two step process:

  1. Authentication: The client proves that they are allowed to use the system. In reply to a verified client, the system provides a JWT Token.
  2. Authorization: The client requests use of the system using the JWT token. The system confirms the token is valid and lets the client use the system.

Authentication can be done through something as simple as passing a username and password for an account that has been created on that system. When the client’s credentials are verified, a JWT token is generated and passed back to the client. This is analogous to the paper that the automotive company gives to their customers. The JWT token is encoded using a secret string of characters that only the system knows about. This is analogous to the signature put on the paper by the technician who performs the work on the car.

Authorization then takes place when a client requests the use of a secured service and provides an encoded JWT token as a part of this request. The system, takes the token and decodes it using the secret. The JSON is then examined to confirm the identity of the person making the request and whether or not they are allowed to use that functionality. Once it is confirmed the client is then allowed to proceed and use the secured system. If no token is provided, the functionality is not made available to the requesting client. There is no need to authenticate every time the client wants to access the system, they only need to validate once and then use the token going forward. Typically a token has an expiration so a client would have to re-authenticate when their token expires. Here is a diagram that visualizes this happy path of the process:

Here is an example of a JWT Token encoded using a secret equal to the value of secret-example-1:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJyYWQuam9uZXMiLCJ1c2VyaWQiOjEyMzQ1NiwiY3JlYXRlZCI6IjIwMjAtMDMtMTk6MTQ6MjU6MzI6MjM0In0.7K8MwxNDMxE2m9uXF1Df_ZlKPDk834khRA4ku1Sp1Qo

Notice that there are three sections of the JWT Token each delimited by a period. They are as follows:

The Header

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

The Payload

eyJ1c2VybmFtZSI6ImJyYWQuam9uZXMiLCJ1c2VyaWQiOjEyMzQ1NiwiY3JlYXRlZCI6IjIwMjAtMDMtMTk6MTQ6MjU6MzI6MjM0In0

The Signature

7K8MwxNDMxE2m9uXF1Df_ZlKPDk834khRA4ku1Sp1Qo

Each of these three parts of the token play a role in verifying whether or not the client providing this token is authorized on the system they are trying to use. This becomes much more clear when you look at the token when it is decoded. The JWT Token above when decoded gives us the following JSON:

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "username": "brad.jones",
  "userid": 123456,
  "created": "2020-03-19:14:25:32:234"
}

The first set of brackets represent the JSON header. The primary purpose of the header is to indicate which encoding algorithm is being used, in our case it is HS256. The second section is the payload. The payload contains data that can be used to confirm the validity of the token such as username, token expiry, etc… It is best practice to not store things like passwords in the payload though for security reasons. The reason being that the header and payload can be decoded and the plain text values become available by anyone who get’s access to the token. They could then use this to authenticate with the system which would give them their own valid JWT token. The purpose of encoding is not to make the values inaccessible, rather it is to confirm that the token itself is valid. This becomes much more clear when you look at how the signature is created.

You might have noticed that we’re missing the signature in the above JSON. That is because the signature is created by first encoding the header JSON, then encoding the payload JSON then those two strings of text are combined and encoded together with the secret which for us was secret-example-1:.

So the signature in our case is created by encoding the following string:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJyYWQuam9uZXMiLCJ1c2VyaWQiOjEyMzQ1NiwiY3JlYXRlZCI6IjIwMjAtMDMtMTk6MTQ6MjU6MzI6MjM0In0.secret-example-1

This results in the following value for the signature:

7K8MwxNDMxE2m9uXF1Df_ZlKPDk834khRA4ku1Sp1Qo

And this is appended to the Encoded Header and Encoded payload to make up the whole JSON Web Token.

This works because the system that the client is trying to access is the only entity that knows what the secret is. And so it uses this secret that only they know to verify that this signature matches. For example if a different secret is used with the value of compromised-token instead of secret-example-1 to encode the signature the token’s signature will be different, for example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJyYWQuam9uZXMiLCJ1c2VyaWQiOjEyMzQ1NiwiY3JlYXRlZCI6IjIwMjAtMDMtMTk6MTQ6MjU6MzI6MjM0In0.2rhOIWkwEA9s9blrCDFEb7U1mQXsZUzTnqp5jZcfzdo

2rhOIWkwEA9s9blrCDFEb7U1mQXsZUzTnqp5jZcfzdo is different than 7K8MwxNDMxE2m9uXF1Df_ZlKPDk834khRA4ku1Sp1Qo, therefore this is a compromised token and the client will not be granted access to use the system.

This works because the system gives this token to the client and the client simply gives it back to the system for the system to decode and confirm that the signature matches the encoding of the header combined with the payload combined with the secret.

Vulnerabilities

It’s quite clear that trying to manufacture your own token would be quite hard. Especially if the secret that the system uses is long, complex and configured in a secure place. But what if someone gets a copy of the original token? This is a vulnerability of JWT. If a hacker is watching web traffic they can inspect the HTTP header and see these tokens being passed to the client browser, copy it and then use it themselves to gain access to the system because the system won’t know that it’s been stolen. Therefore it is critical that all communications with the client browser takes place over HTTPS rather than HTTP. HTTPS will encrypt the traffic so that the plain text value of the token is not visible even if a hacker is monitoring the traffic.