A quick introduction to Json Web Tokens (JWT) and JOSE

2022-07-06 - 8 min read

Authentication in APIs tends to use Basic Auth (sending username and password). Later, OAuth became common and used random tokens, so-called Bearer tokens. These tokens were initially random tokens that are stored in the database. This mechanism gave more possibilities, such as scoping and revoking access. However, there are some downsides:

  • The application must check the token with the database on each request. This is not an issue for most applications, but it surely is for distributed applications, where the database is the most expensive part in terms of costs and performance.
  • If other applications (especially microservices) want to use this token, they must validate it against an API endpoint of the system that issued the token. This puts a lot of stress on the authorization server.

The bottom line is that performance may be an issue when using random tokens. However, by the OAuth standard, clients may not make any assumptions on the format of the Bearer tokens. Json Web Tokens (JWT) make clever use of that aspect to solve the above-mentioned problems.

What are Json Web Tokens (JWT)?

As opposed to random tokens, JWT carries data, called the payload. The tokens are composed of three parts: header, payload, and signature. A sample token looks like this:

eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDQzOTE1NzMsInN1YiI6IjEyMyJ9.cB0BOaUVIjxpjsu2ZsmrVBNGo4CKEDmwugFcSqhaFN8

At first, this might look like gibberish, but you will recognize this format quickly when using it more often. It always starts with "ey” and contains exactly two dots that separate the parts. Each part is base64url encoded. The first part decodes to {"alg”:”HS256"}. In this case, this indicates that we are using an HMAC SHA256 signature, and hence are using a secret key, but more on that later. The second part decodes to {"exp”:1644391573,”sub”:”123"} , which indicates the expiration time (as an unix timestamp), and the subject, which is our user id. The last part is the signature required to verify the token's authenticity.

There are some pleasant aspects to this token. First, it is safe to use in URLs, which is convenient for use in web applications. Second, we can immediately see that the token is expired and to which user this token belongs, without looking it up in a database.

The consuming application needs to verify the token's authenticity before it trusts its data. To keep it simple for now, copy over the token in jwt.io and check that you can validate the signature with the secret, which is AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow (base64 encoded). Sssh!

What are the different types of tokens?

The example above uses a secret key. Therefore, each application that wants to use the token also needs the secret, which is suboptimal for distributed systems. The secret key allows you to sign your tokens and hence login as whoever you want, so we have to protect it carefully. Luckily, another way of signing tokens allows everyone to verify tokens without possessing a secret.

Using RSA asymmetric encryption, we can sign tokens using a private key and verify them using the public key. The latter may be distributed freely. If you want to snoop on the keys used by Google accounts, you can do so here: https://www.googleapis.com/oauth2/v3/certs

It contains two keys (at the time of writing). Each key has a property ”alg”: "RS256”, which means it's intended for 256 bit RSA encryption. It also has a "kid” property, which stands for "key id”. Tokens that can be verified using these public keys will have the same "alg” and "kid” properties present in their JWT header.

We have seen the HS256 and RS256 algorithms, but there are more, such as RS512 (similar to RS256, but longer signature). However, these two are the most common. You may come across the 512-bit encryptions, but other algorithms are rare.

How are they used?

In practice, you will add more information to the payload, called ‘claims'. For example, the username and e-mail address can be added, so the application doesn't need to look them up either. This is not just to save the database call but becomes more critical when using microservices.

Imagine that our application uses Google accounts to sign in. We do not have access to any secrets or databases, but we can still validate the token with the public keys we have seen. And when we have verified the signature and expiry date, we can take all claims for granted. Hence, our app knows who is logged in without needing any storage or API call (if we have the public keys in cache).

How are they created?

Tokens with a secret key can be easily created using a JWT (or JOSE) library, usually in a one-liner. A little more work is required when using RSA tokens. Then, at least we have to generate a key pair. Also, we should publish the public key on a jwks-endpoint, as in the Google example. This might be a static resource. However, the reason that it contains multiple keys is key rotation. That concept is crucial because it allows you to roll out new keys without invaliding current sessions. Therefore, the identity provider (IdP) should store the key sets in its (encrypted) database.

Cronjob for key rollover in Flowlet.app

The screenshot above is a low-code flow created using Flowlet. A new key pair is periodically generated and published on the jwks-endpoint, in this case at: https://mailsecurity.flowlet.app/api/certs

Are they secure?

The encryption algorithms used in JWT are well known and secure. No worries on that part. However, one aspect of JWT is less secure than what we had using the random Bearer tokens. That is token revocation. For example, how do all systems know that this token shouldn't be valid anymore when a user logs out? The token is distributed across multiple systems that use the token without checking it at the IdP. So, in fact, we can't revoke such tokens.

A simple solution to this problem is to shorten the time that a token is valid. For example, a JWT might be valid for only 15 minutes. After logout, the client destroys the token, and rest assured that nobody can have a valid token after 15 minutes have elapsed. You may leverage OAuth refresh tokens to allow longer periods without logging in. This is the point where the IdP can check that the user did not log out, and it only has to be done at most every 15 minutes.

And what is JOSE?

JOSE is a set of open standards for exchanging information securely over the web, between two parties: a browser and a server, or among different servers. It contains the following standards:

  • Json Web Signature (JWS) is like JWT, but without the restrictions on its payload. So, JWT's general format, header, and signature come from JWS. It only adds structure to the payload, including properties like "sub” and "exp”, which standardize authorization tokens. If you want to send binary data with a signature, then JWS is for you.
  • Json Web Encryption (JWE) is a standard way of encrypting raw data.
  • Json Web Key (JWK) is a standard format for a public or private key. As seen earlier in this post, a Json Web Key Set (JWKs) is a collection of keys.
  • Json Web Algorithms (JWA) is a collection of encryption algorithms, including HS256 and RS256.

These standards cover most of the encryption needs for web applications. And as you can see, it's pretty easy to create a JWK using a JWS library as well.

Implementation using No-Code

We have already seen the example from MailSecurity.app, whose backend is completely built using low-code in Flowlet. Their marketplace contains a free package that directly gives you the jwks-endpoint among other OpenID endpoints and related building blocks.

Conclusion

JWT is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. The JWT token is composed of 3 parts: the header, payload, and signature. The header typically contains the type of token, the algorithm being used, the usage duration, and sometimes other metadata about the token. The payload is where we find claims such as user id, group id, or if the token was generated on behalf of another party. The signature proves that this token arrived unaltered from its sender to its receiver and hence can be trusted by any service reading it without the need to call external APIs or databases.