Stateless sessions thanks to JSON Web Tokens.

06.10.2021Ricky Elfner
Cloud jwt Rust API Go Distributed Systems Java Microservices

JSON Web Token, also known as JWT ([dʒɒt]), is used to exchange so-called claims. This is mainly used in the area of authentication for microservices and serves as an alternative to classic web cookies. JWT has also been an open and certified standard since May 2015 (RFC 7519).

What is a JWT?

For this we start with the basic principle of a token. In everyday life, this can be compared to an ID card and helps a person to identify themselves based on the entries on it. Objects that apply to authentication always come from a third and independent authority. This checks whether this information on a token, in this case the ID card, is correct. If this is the case, there is a signing.

This principle can also be applied to the digital world. Because the previously mentioned entries are called claims in the digital world. This procedure means that database queries are no longer necessary. Because the user receives a JWT after a successful login, which contains all important information about the user. This means that the session no longer has to be saved on the server and is therefore also called a stateless session.

What is JWS and JWE?

If you look at it closely, there is no pure JWT: Because it is either a JWS or a JWE. Because of this, the JWT is actually a kind of abstract class. JWS stands for JSON Web Signature and is an implementation of a JWT which is encrypted with a secret key. But an unsecured JWT is also a JWS, because the encryption algorithm is simply set to none.

On the other hand there is JWE, which stands for JSON Web Encryption. This topic will be considered separately below. This variant consists of a different structure compared to JWS.

However, these two additional definitions only apply as long as “compact serialization” is involved. Once we’ve discussed the basic structure of a JWT, we’ll go into more detail about serialization.

Building a JWT

A JSON web token consists of three parts:

  • a header,
  • a payload,
  • and the signature.

The header

This is created as a JSON object and contains two key-value pairs. The first pair (alg) describes the algorithm used to encrypt the signature. Here, for example, the options RS256 or HS256 are available. However, you could also do without encryption, but this is not recommended under any circumstances. The second property typ always contains the hardcoded value JWT. This header is also known as the JOSE header. JOSE is the abbreviation for Javascript Object Signing and Encryption. The associated data structure looks like this:

1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}

The payload

This is also a JSON object, which now contains the actual information, the claims. There are some predefined claims that you can use, or you can create your own claims. The only claim that is really required is the subject claim (sub), which contains the user ID. The claims are divided into three categories, registered, public and private. The registered claim, registered, is the claim that has been pre-determined for all JWTs to be considered the default.

  • iss → issuer is the issuer of the JWt

  • aud → audience tells who this JWT was issued to

  • exp → expiration defines when the token expires

  • nbf → not before decides, but when the token is valid

  • iat → issued at time describes when the token was created

  • jti → JWT ID

There are also public claims, here developers can register various claims themselves in the IANA JSON Web Token Claims registry. This way you can be sure that there are no duplicate claims. And finally there are the private claims, which are usually used specifically for the application.

1
2
3
4
5
6
{
  "sub": "1234567890",
  "name": "b-nova",
  "admin": true,
  "iat": 1516239022
}

The signature

The signature is created by Base64 coding of the header and the payload, as well as the specified encryption method within the header. This JSON Web Signature (JWS) is also an RFC 7515 standardized standard.

The final signature is created as follows:

1
2
3
4
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

The parts of the token are separated with a punk, since the point does not occur within a Base64 encoding. In this case, this output is generated:

1
2
3
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImItbm92YSIsImFkbWluIjp0cnVlLCJpYXQiOjE1MTYyMzkwMjJ9.
YDrKvpHj3s2B1Xm4SWQuFzs4fAkrUkDuyofBGdeQ27E

(Line breaks are inserted for better readability.)

With this format, it is now possible to pass on the JSON Web Token via the URL and exchange it between client and server.

JWS Compact Serialization vs. JWS JSON Serialization

The JWS Compact Serialization is the classic variant of a signed JWT. Because this is so compact that the token can be sent via the URL, for example. This consists of the three parts described above: header, payload, signature. This means that only one signature can be placed over the header and the payload.

With the JSON serialization variant, the previously described disadvantage is eliminated with only one signature. Because here it is possible to place several signatures over the same payload and header. However, this type has the disadvantage that it is no longer URL-safe. Also, due to the spelling used, this format is no longer very compact.

The structure is divided into 2 top-level parts, payload and signatures. The signatures part is in turn divided into three further parts protected, header and signature. The payload\ part is the complete JWS payload with a Base64 encoding.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
  "signatures": [
    {
      "protected": "eyJhbGciOiJSUzI1NiJ9",
      "header": {
        "kid": "2010-12-29"
      },
      "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO5vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-OkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
    },
    {
      "protected": "eyJhbGciOiJFUzI1NiJ9",
      "header": {
        "kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"
      },
      "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
    }
  ]
}

The protected attribute corresponds to the header within a signatures object, also in Base64 coding.

Cookies VS. JWT

The traditional way

First, the user logs in via the browser with his credentials, such as the e-mail address and the password. This is then sent to the server using a POST method. This server receives the data and checks whether they are correct. This server is therefore responsible for the authentication, it must also save the current session on the server and send the user a corresponding session ID as a cookie. If the user now makes another page call, this must also send the corresponding session ID cookie. The server then has to go back and check whether there is a session with the sent ID. If so, the server sends the appropriate response to the client.

The modern way

Here, too, the process begins with the user logging in by sending their credentials. But here comes the difference to the previous variant, because nothing is stored on the server here. Because the server now creates a signed JWT with its own secret key. This JWT is now sent back to the user’s browser. If the user now sends a new request to the server, it must also send the JWT. The server uses the secret key to check whether the token is still valid or not. If this is valid, the token can be deserialized and the corresponding user information is now available to the server without having to save it. This now offers the advantage that the JWT can now be used on different servers if the secret key is shared. This saves the user another login and the second server does not have to save an additional session.

Benefits of JWT

Once you start using JWTs, it is no longer necessary to save a session on the server, so you can save a lot of storage space. Because instead of saving this, the necessary information is supplied with every request and the token is verified again. The server only has to store the secret key in order to check the validity of the token or to create new tokens.

Since there are already implementations for all common programming languages, JWTs can be used quickly in your own applications. These languages include, for example, Go, Java, Rust, JavaScript or Python.

As you could already see from the structure, this is a very minimal amount of necessary content to enable very reliable authentication. By reducing the overall network overhead, performance can be significantly increased here.

Because there is the possibility of creating your own/private claims, JWT is very versatile and can be used not restricted to specific areas of application.

JWT Transport

Although JSON WebTokens is a defined standard, it does not define how a JWT should be transmitted. In most cases this is done over HTTP using the Authorization Bearer header. The same principle is also used with OAuth 2.0. As soon as one speaks of a bearer token, this means that the owner can use this token without proving his ownership. Therefore, the transmission should always be encrypted.

Construction of a JWE

Again, this is a token, which is again separated by dots, but consists of five parts. Since this is a slightly broader topic, we’ll just briefly look at how it’s set up.

JOSE header

This has the same structure as the content of a JWS header before. However, there are now two new elements that can be added. The first element is enc and specifies the content encryption algorithm. The second element anc defines the encryption algorithm for the content encryption key (CEK).

JWE Encrypted Key

The content of this field is encrypted with the recipient’s key.

JWE initialization vector

This is a randomly generated number that is used with the secret key to encrypt the data.

JWE Ciphertext

This is the actual encrypted payload. This is created using the Content Encryption Key (CEK), JWE initialization vector and the Additional Authentication Data (AAD) value. The encryption algorithm is determined by the enc defined in the header.

JWE Authentication Tag

The authentication tag is used for an integrity check.

Conclusion

Since JWT is now very common and offers a preferred variant over the traditional session ID cookies, you should definitely deal with this topic. But since JWT can be very versatile due to the different variants, it is important to know the differences.


This text was automatically translated with our golang markdown translator.

Ricky Elfner

Ricky Elfner – Denker, Überlebenskünstler, Gadget-Sammler. Dabei ist er immer auf der Suche nach neuen Innovationen, sowie Tech News, um immer über aktuelle Themen schreiben zu können.