In the past, I worked with JSON Web Tokens (JWT) a couple of times. To back up and share my knowledge, I’ll write a miniseries about the topic. In the first article, I gave an introduction to the most important file formats regarding certificates: pem, pub, csr and crt. In this second article, I want to introduce the most crucial concepts of JWT. The third article will include code for how to work with JWT using Java. The final article will include some more in-depth topics about verification and signing.
TL;DR
JSON Web Tokens (JWT) are containers for data in JSON format that can be signed and encrypted. Because they are meant to be as small as possible and offer a number of specified features, they are very useful for many tasks, for example, as access tokens for single page applications (SPAs).
Some Acronyms
There are some abbreviations in the context of JWTs that need some introducing.
JWT
The JSON Web Token itself. JSON stands for JavaScript Object Notation and is a data format often used in web applications.
JWS
JSON Web Signing. Means the fact that a JWT can be signed either using the symmetric HMAC algorithm or several asymmetric cryptography algorithms like RSA. That way, the origin of a token can be trusted because only this origin has access to the private key to sign the token. Using the matching public key, anyone can verify the token. Signing a token creates immutability of the token, meaning that nobody can change the content of the token without changing the signature. Signing is not encrypting, hence signed but unencrypted tokens can be read by anyone.
JWE
JSON Web Encryption. Means the fact that a JWT can be encrypted, again using asymmetric cryptography like JWS. Anyone can use the public key to encrypt the token and only the entity in possession of the private key can decrypt the token and read its content. Encrypting a token creates concealment of the tokens content. Encrypting is not signing. Because encrypted tokens can be created by everyone, encryption does not guarantee a certain origin.
Structure of JWT
JWTs consist of three parts that are separated by a period:
The header and payload are JSON objects. The signature is only appended if the token is signed.
Example of signed token:
In JSON format, this token has the following header:
In JSON format, the token has the following payload:
Example of unsigned token:
In JSON format, this token has the following header:
In JSON format, the token has the following payload:
As you can see, the unsigned token does not have a signature part because the header documents the algorithm (“alg”) with “none”. It’s good practice to disregard an unsigned token as invalid. A common attack named “signature stripping” consists of the removal of the signature part and changing the header so that “alg” is “none”. A legit unsigned token cannot be distinguished from a token that is a victim of a signature stripping attack. Hence, every unsigned token should be disregarded.
The header and payload is Base64url encoded. Base64url is a special variant of Base64 in which some characters are encoded differently. For example, “+” is encoded as “-“ and “/” is encoded as “_”. This makes the string safe to be used in an URL.
Claims
A claim is a key-value pair. Claims can be found in the header and in the body of JWTs.
Typical claims found in the header are, for example:
Header Claim | Description |
---|---|
"alg": "HS256" | Algorithm for signing/decrypting |
"typ": "JWT" | Media-Type |
Claims with pre-defined meaning, so called “registered claims” found in the body are:
Body Claim | Description |
---|---|
"iss" | Issuer. String or URI of the party that created the JWT. |
"sub" | Subject. String or URI of the entity the JWT holds information about. |
"aud" | Audience. Strings or URIs that describe the intended recipient of the token. |
"exp" | Expiration. Point in time from which the token should be regarded invalid. Unit is seconds since epoch. |
"nbf" | not before. Opposite of exp. Point in time from which the token should be regarded valid. Unit is seconds since epoch. |
"iat" | issued at. Point in time the token has been created. Unit is seconds since epoch. |
"jti" | JWT ID. Distinct identification of that specific token. |
The claims mentioned above are “public claims” and are part of the specification of JWT. That means that an application should not assign a different meaning to these claims. For application specific purposes, any number of “private claims” can be created.
Access Token vs Refresh Token
In the context of authorization, there are the concepts of access tokens and refresh tokens. Because these can be implemented using JWTs, here’s a quick explanation of these concepts, taken from Auth0.
Access tokens are used for the actual data access on the resource server, for example an SPA requesting data from the backend. In this scenario, the SPA gains an access token from the backend / resource server. This access token is then send with every request to the backend. To accelerate the processing of the request, the access token is signed and is used as the only proof that the request is legit. No call to the Identity Provider (IdP) is performed by either the backend nor the SPA.
After the access token expired, the SPA creates a refresh token and sends it to the backend to request a new access token. These refresh tokens expire quickly because they are only meant to get another, relatively long-living access token. Upon receiving a refresh token, the backend uses the IdP to validate the request and creates a new access token, which is send to the SPA to be used in future requests.
Further Reading
The main knowledge base for JWTs is jwt.io. Here, you’ll find an informative free ebook and an online validator / debugger for JWTs.
The page is hosted by Auth0. They also host a nice introduction article about JWTs, just like the one I published here. I highly recommend reading this text because the authors highlighted some aspects I left out for brevity.
In the next article, I will present some Java code to create and validate JWTs.