5 different approaches to invalidate JSON Web Tokens

Michael Bruns
6 min readMay 23, 2019

When it comes to authentication at web services, JWT offers a simple but effective method. But it also comes with some downsides. Because the tokens are stored within the client, the server normally has no clue how many tokens are in use. Once a user now changes his password or manually wants to invalidate all sessions, it seems as if this exceeds JWT’s capabilities. I have found five ways to keep track of all sessions or at least to have the ability to invalidate all sessions in an uncomplicated way.

1. Different IDs

This method is the easiest one to think of I guess. In each signed JWT a unique ID is stored within the payload which is also saved in the database. The following code block shows an example of a payload using this method:

{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"id": "P3JU4KM3" # this part is the important one
}

By adding the id claim to the payload, you can link the token with a database entry. The database entry should at least contain the token ID and a unique field pointing to the session’s user.

Upsides

  • each token can be invalidated manually
  • you can keep track of the number of signed tokens

Downsides

  • higher data usage by assigning a unique token to each request (each token gets bigger because of the new claim) — loss of statelessness of JWTs
  • you have to save all ids in the database
  • in case of a general invalidation of all tokens, each token has to be invalidated manually inside the database

2. Increasing counter

One major downside of the first method is that it increases the size of the JWT. In addition to that, it requires the web server to keep track of each token signed by storing them inside a database. This approach simplifies the storage process and moreover reduces the increasing size of the JWT.

When using this technique, you have to set a new claim e.g. called ctr which counts individually for each user’s sessions. The following code block shows an example of a JWT’s payload:

{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"ctr": 4 # this part is the important one
}

In this example, the value 4 indicates that the web token’s content has been changed three or four times (varies from the set starting index). If you want to, you can also change the algorithm of increasing the counter to obscure the number of changes made to the JWT. By increasing/changing the counter, all previously signed tokens become invalid and cannot be used anymore.

While in the first method all signed tokens with their IDs had to be stored, in this method only the current counter has to be stored inside the database.

Upsides

  • a low-cost and efficient method to invalidate all tokens
  • 1 small database entry per user
  • small extra claim within the JWT

Downsides

  • higher data usage by adding the (relatively small)ctr claim to each token
  • tokens cannot be invalidated individually
  • still the need for database entries

3. Refresh tokens

The concept of so-called ‘refresh tokens’ (in my eyes, this name can lead to misunderstandings) was invented within the OAuth 2.0 protocol. Disclaimer: the following paragraph contains only a basic description of how to use refresh tokens. I therefore highly recommend reading the article ‘Refresh Tokens: When to Use Them and How They Interact with JWTs’ by Sebastian Peyrott in order to get a deeper understanding of how refresh tokens work and how to use them properly.

When authenticating with the authorization server, the client receives a rather long-lived refresh token. This token can be used to get access tokens which then can be used to access specific resources. Access tokens, in turn, are short-lived in order to prevent major leaks or abuse. While the client only stores one refresh token, there can be several stored access tokens for different endpoints.

This concept contains a further problem solution in so far as you can store each refresh token in a database. By doing so, you are able to keep track of them and check them when a client tries to request a new access token.

Upsides

  • a low-cost method to blacklist refresh (!) tokens
  • not losing stateless authentication
  • relatively small database usage by only storing refresh tokens
  • the impact of the leakage of access tokens can be reduced by using short-lived tokens
  • the lifetime and reusability of access tokens can be limited (e.g. by making them really short-lived or IP-bound)

Downsides

  • higher data usage by having a refresh token and (several) access tokens separately
  • access tokens cannot be invalidated/blacklisted — clients can still access resources (in the best case for a short time only) even if the refresh token is marked invalid
  • still the need for database entries

4. Renew server-side secret

When signing JWTs, the server uses a server-side secret which you can either use for all clients or, in this case, use different secrets for each client. By doing so, you can easily invalidate a user’s signed JWT. You still have to keep track of which secret to use for an authenticating client. The best way to accomplish this is to save the secret to a database and link it to the user. If a JWT is then signed, you have to add a claim which can be used to identify the secret inside the database (normally, the sub claim should do this job).

Upsides

  • a low-cost and efficient method to invalidate all tokens
  • only one database entry per user

Downsides

  • relatively big database entry by using a different secret for each user
  • possibly higher CPU usage by generating a new secret for each new user
  • possibly higher data usage by adding a new claim to find the linked database entry (if the sub claim is unusable)
  • tokens cannot be invalidated individually
  • still the need for database entries

5. JWT claim 'iat

There are a few public claims, which are stated within the official JWT documentation. Furthermore, there are many more claims which are defined in the IANA JSON Web Token Registry. At the very beginning of this table, you can find the iat claim which stands for “Issued At”. This claim can be used to set a date when the token was signed and issued. In addition to that, you should save the date of the most recent invalidation in a database. Under these conditions, when a client tries to authenticate with a JWT the provided iat claim can now be compared with the recent invalidation date in the database. If the iat claim is before the recent invalidation date, the authentication is denied. One prerequisite to do so is that the JWT contains a claim to identify the linked database entry (normally, the sub claim should do this job).

Upsides

  • a low-cost and efficient method to invalidate all tokens issued before a specific timestamp
  • only one database entry per user

Downsides

  • possibly higher data usage by possibly adding two new claims to find the linked database entry (if the sub claim is unusable) and the iat claim to set the date of issuance
  • tokens cannot be invalidated individually
  • still the need for database entries

Conclusion

All of these five different approaches to get rid of the difficulty of invalidating JWTs come with their own ups and downs. For each individual case, you should evaluate which choice to go with. For my own small project, which is why I came across this problem at all, I chose the 5th approach.

Please note that these approaches are just ideas I have encountered or considered on my own. There may be many more successful concepts which are more efficient. If you would like to share one, feel free to leave a comment.

--

--