Introduction to JWT (Also JWS, JWE, JWA, JWK)
The security and privacy of users' data have been a growing concern for the past few years. Understanding JWT will give you an edge over the other software engineers.
Join the DZone community and get the full member experience.
Join For FreeThe security and privacy of users' data have been a growing concern for the past few years. At the same time, JWT, as one technology to combat it, has been used more and more. Understanding JWT will give you an edge over the other software engineers. JWT might seem simple at first, but it is pretty hard to understand.
In this article, we will explore mainly JWT and JWS. In addition, we'll also go through JWE, JWA, and JWK quickly. This article aims to make the reader understand the concept of JWT without diving too deep into the topic.
What Are They?
Before taking a deeper look, it‘s better if we know the connection between JWT, JWS, JWE, JWA, and JWK.
From the diagram above, we can see that:
- JSON Web Token (JWT) is an abstract that is represented in the form of JSON Web Signature (JWS) and JSON Web Encryption (JWE).
- JSON Web Signature (JWS) and JSON Web Encryption (JWE) use signature and encryption algorithms defined in JSON Web Algorithm (JWA) as a way of securing themselves.
- The public key of the signature algorithm defined in the JSON Web Algorithm (JWA) can be hosted as JSON Web Key (JWK).
Now that we know their relationship, let’s take a deeper look at the JWT, JWS, JWE, JWA, and JWK.
JSON Web Token (JWT)
First, let's see the definition of JWT defined in the RFC 7519.
"JSON Web Token (JWT) is a compact claims representation format intended for space-constrained environments such as HTTP Authorization headers and URI query parameters. JWTs encode claims to be transmitted 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. JWTs are always represented using the JWS Compact Serialization or the JWE Compact Serialization."
From the text, we can understand that JWT is not a structure but a set of claims in the shape of either JWS or JWE as its way of securing itself. In the most basic form, the difference between JWS and JWE is that everyone can see the payload of JWS while the JWE one is encrypted.
In this article, we will explore more about JWS than JWE.
JSON Web Algorithm (JWA)
JWA (RFC 7518), which stands for JSON Web Algorithm, is a specification defining which hashing and encryption algorithm to make a JWT.
For example, the following are the hashing algorithms we can use to create a JWT with a JWS structure.
JSON Web Key (JWK)
JWK (RFC 7517) stands for JSON Web Key. JWK is a JSON data structure that contains information about hashing function's cryptographic key. It's a way to store your hashing key in JSON format.
{
"kty":"EC",
"crv":"P-256",
"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"kid":"Public key used in JWS spec Appendix A.3 example"
}
The JWK is usually used to host a public key for a hashing function with an asymmetric key (private key and public key), so the consumer can get the key by themself.
JSON Web Signature (JWS)
JWS (RFC 7515), which stands for JSON Web Signature, is one of the structures used by JWT. It's the most common implementation of the JWT. JWS consists of 3 parts: the JOSE header, payload, and signature.
The following is an example of JWS in compact serialization.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
If we decode the JWS, we will get JOSE (JavaScript Object Signing and Encryption) header:
{
"alg": "HS256",
"typ": "JWT"
}
The payload:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
The next part is the signature. We won't decode it because it's a bytes value.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The most common question related to JWT (in JWS structure) is what makes it secure since everyone can decode the JWT and see its data. The JWT is secure because not everyone can create it, but only the one with the secret key.
JSON Web Encryption (JWE)
JWE (RFC 7516), unlike JWS, encrypts its content using an encryption algorithm. The only one that can see what is inside the JWT is the one with the key.
The structure of JWE compact serialization is as follows:
BASE64URL(UTF8(JWE Protected Header)) || ’.’ ||
BASE64URL(JWE Encrypted Key) || ’.’ ||
BASE64URL(JWE Initialization Vector) || ’.’ ||
BASE64URL(JWE Ciphertext) || ’.’ ||
BASE64URL(JWE Authentication Tag)
Let's take a look at one example:
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.RD09fEltrYPVNoGt2KY1Odv_5eDxkU4VX1f__P8b9zl9uzh5bmvvJy35dL-hYlUib1g63qnWBEfeSyDk5cAIQiMt6PZCBQzuWQJQlQtuo2UPLZznmLPqah37uHKB4a57q_lWf_W9soyZbO7Zj7QRNz4ZR4s5ozRHArSZcc1pAL-pYuHKyeh6Ey8t4bk66wkthjjfOjXvIfOlgbemhibegmE4GpQL6F-m0teqcAE-OxkaBRTmmb4AD5HdrCJWCIIuC52fzuWrhcoNmHM74ggtWUUjlHaKpwcVE-IWINTFaz5Pi9u4U3vnVNOZwDwB0TLSQvqnPwTZ-bYWNj8vH4TS_w.Pjo5QK1u1otxgcuBR7e8ew._OElhHugS2L6Kp04HhbFt6dLij_KXhO654RmT4JKyswYBX0wqRWt7ZzAE6eCHfJSJdMQYxqVSNloGb4OSIzYcTEo174lBZBINkHW-w2K6E0.QBDgBFizm80HLVkZvfBPCg
The example uses the RSA_OAEP_256
key management algorithm and AES_128_CBC_HMAC_SHA_256
as its encryption algorithm. If we decrypted the JWE, we will get:
{
"iss": "https://codecurated.com",
"exp": 1651417524,
"iat": 1651417224
}
Decoding the JWT
Now let's take a deeper look at the JWT by using the following example:
eyJhbGciOiJIUzI1NiIsImtpZCI6IjIwMjItMDUtMDEifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJyaWxpYW4gRmlyZGF1cyIsImlhdCI6MTY1MTQyMjM2NX0.qsg3HKPxM96PeeXl-sMrao00yOh1T0yQfZa-BsrtjHI
The JWT in the example is a JWT with JWS compact serialization structure. We need to split the JWT by .
(dot), and Base64 decodes them to look at what's inside it.
After splitting and decoding the JWT, we will get three parts:
- JOSE (JavaScript Object Signature and Encryption) Header
- Payload
- Signature
JOSE Header
eyJhbGciOiJIUzI1NiIsImtpZCI6IjIwMjItMDUtMDEifQ
After doing a Base64 decode on the string, we will get:
{
"alg": "HS256",
"kid": "2022-05-01"
}
This part of JWT is called the JOSE header. With the JOSE header, the JWT can inform the client how to handle the JWT.
Let's break down the two fields we have in our JWT:
alg
: contains the information regarding the signing algorithm of the JWT.kid
: contains the information of the id of the key used for verifying the JWT. We will explore more about this in the JWK section.
alg
is the only mandatory header, and it's the only one needed for most cases, but there are many more headers.
Payload
With the JOSE header out of the way, let's take a look at the second part, the payload.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJyaWxpYW4gRmlyZGF1cyIsImlhdCI6MTY1MTQyMjM2NX0
After we decode it:
{
"sub": "1234567890",
"name": "Brilian Firdaus",
"iat": 1651422365
}
This part of the JWT is called the payload. There are fields with the term JWT claims ( sub
, name
, iat
). So, now seems like a good time to talk about JWT claims.
There are three types of JWT claims:
- Registered Claims
- Public Claims
- Private Claims
Let's break it down one by one, starting from Registered Claims. Registered claims are claims that have been documented initially in the RFC 7519.
iss
(Issuer): indicates who is the issuer of the JWT.sub
(Subject): indicates the user's id requesting the JWT.aud
(Audience): shows who's the intended consumer of the JWT.exp
(Expiration): the expiration time of the JWT.
You can check the complete list here. None of these claims are mandatory, as dictated in the RFC 7519, but they are essential in securing your JWT.
The next type of claim we'll explore is private claims. These claims can be anything. It's up to the JWT creator or consumer to determine the claims name and function.
Warning: When specifying private claims, you need to be careful not to cause any collision in the name.
The last one is public claims, which is a type of claim that is publicly registered through IETF.
Most people don't have any use case needing them to register their claims. For best practice, you can search the list of Public Claims and use the one suitable for your use case.
Signature
If you've come to this part, you might wonder what makes the JWT secure since everyone can see its content. Well, the payload of the JWS is intended for everyone to read. What makes the JWT safe is the consumer can verify who is the one issued the JWT.
The signature part in JWT is created by using a hash function. If you're not familiar with a hash function, it's an algorithm that maps an object to another object. A hash function has two crucial traits which make it suitable to secure the JWT:
- It only works one way
- The results of hashing process are always the same
In this article, we'll explore 2 of the most common hash function used in JWT:
- HMAC SHA-256
- ECDSA256
Now, let's break apart the signature we use in the example:
qsg3HKPxM96PeeXl-sMrao00yOh1T0yQfZa-BsrtjHI
Which is generated using HS256
MAC algorithm with the secret key (in base64):
7TgIAQCcYUA27bCI5+m7InRwp/mzQ+ArnFW/4c0Q51U=
The HS256
MAC algorithm receives bytes value as its secret key parameter and produces bytes value as its output. So, unlike the header and the payload, we will get bytes value if we try to decode the signature.
Let's explore the digital signature or MAC algorithm more thoroughly.
HMAC SHA-256
The first algorithm we'll explore is HMAC SHA-256 (HS256
), a MAC algorithm using a hash function with a symmetric key. Hashing function with a symmetric key means only one key for the hashing function. So, the producer and consumer of the JWT will use the same key to sign and verify the JWT. The advantage of using this algorithm is it doesn't need many CPU resources to create the hash.
Info: The minimum bytes length recommended for the hs256 secret key is 32 bytes. The secret key must be generated with a Cryptographically-secure pseudorandom number generator to ensure its randomity.
ECDSA-256
ECDSA-256 (ES256
), unlike HMAC, is an algorithm that uses hashing function with an asymmetric key. Hashing function with an asymmetric key means we will need to generate two keys. One key is called a private key, which can be used to both sign and verify the JWT signature. The other key is called a public key, which can only be used to verify the JWT signature.
As their name implies, a public key can be stored in a public space (usually as a JWK), so the client that needs to verify your JWT signature can get it quickly. In contrast, a private key must be secured and treated as a credential.
Which Hashing Function to Choose?
We have learned two algorithms, HS256
and ES256
, but when to choose one instead of another? You can easily decide about it by thinking about whether the producer and the consumer of the JWT are not the same components.
If the consumer of the JWT is the same component, then you can use the HS256
algorithm. This hashing function's most common use case is when you're making an authentication system using JWT.
On the other hand, if the JWT is produced and consumed by a different component, you can use ES256
. This way, you can secure your key and ensure that no one else (even the public key owner) can create a JWT on your behalf.
Warning: Some people consider HS256 a kind of anti-pattern because JWT is supposedly used to increase security, but most of the use case that uses this algorithm decreases security.
For example, suppose you plan to use JWT as an authentication session instead of a database. In that case, your system is more insecure because you can't expire the JWT, so you can't kick sessions.
Storing Public Key as JWKs (JSON Web Key Set)
If you plan to let the public consume and verify your JWT, it's recommended to host the public key as a JWKs on a URL. This way, if the consumer wants to verify your JWT, they can query a specific URL hosting the JWKs and get the public key.
{
"keys": [
{
"kty": "EC",
"kid": "2022-05-01",
"x": "g_pYyqY7Htj8Aa989Ura0_mwRdqJPEnhknKzaUrztj8",
"y": "MwOFYLE-VYre92hU0iDjNx36dk7cX6xdGgdgLIPt6Ts",
"crv": "P-256"
},
{
"kty": "EC",
"kid": "2020-01-01",
"x": "6bw04ZlSMjxVzC7gXv75XAposOVTONh45ZPR0AeYaoU",
"y": "vYyCSIt0m5k4Q5A_uW8h3nEYJvgA8PgREErLcaiAHgQ",
"crv": "P-256"
}
]
}
You might've noticed more than one key in the JSON, which is why it's called JSON Web Key Set. If your product uses more than one key, you can host every key in the JWKs. To identify which key to use, you will need to add the kid
or key id field in your JOSE Header.
{
"alg":"ES256",
"kid":"2022-05-01"
}
This way, the client will know which key to get by comparing the kid
in the JOSE header JWT to the one in the JWK. The other fields, combined, will make the public key.
Takeaway
In this article, we've learned that:
- JWT is an abstract concept about how to allow one or more parties to exchange information securely. The implementation of JWT comes in the form of JWS or JWE.
- The difference function between JWS and JWE is that JWS allows everyone to see its payload, while JWE doesn't allow it by using an encryption method.
- What makes JWS considered secure even though everyone can see its payload is that the creator of the JWS can be verified by its signature using MAC or Signature Verifying algorithm. This way, the consumer can be sure that the one created the JWT is the one intended.
- We've explored two types of algorithms,
HS256
andES256
.HS256
is suitable when the producer and the consumer of the JWT are the same components, whileES256
is suitable when the producer and the consumer are different components. - We can use JSON Web Key Set to host a public key for hashing function with an asymmetric key. You need to set the
kid
field in the JOSE header of your JWT so the consumer can compare it to the one in the JWKs to get a compatible key.
Alas, I want to thank you for reading until the end. The next step you can take is to learn how to implement it. There are many libraries for popular programming languages that you can check.
Published at DZone with permission of Brilian Firdaus. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments