Among the many criticisms given to stateless tokens, specifically JWT, the only one I ever found reasonable was that stateless tokens usually have no built-in or simple means of revokation.
JWT lovers defend by claiming that a short enough expiry is enough of a revokation scheme for them. The JWT haters continue to claim that isn't a revokation scheme at all, because you're not really revoking anything when you need to revoke it.
Both sides make sense to me - some application owners using JWT just want simplicity and find the JWT ecosystem wide enough to easily include a "fast" authentication scheme into their application (ignoring the fact that some of those JWT libraries literally do database lookups for each request alongside verifying the JWT statelessly, practically nullifying the performance gain!), but it's also true that they don't really have a solid revokation scheme, and an attacker who gets their hands on the wrong JWT can wreak havoc pretty quickly while your customers ask you why you can't stop them.
So in that context, let's consider what an actual revokation scheme would look like, where one doesn't sacrifice the performance benefits of JWT. Disclaimer: any strong and scalable revokation scheme won't make JWTs "simple" in any sense anymore, especially because I don't know of any 1-click ecosystem solution for it yet (yes, you can probably make a business out of this). So if the reason you use JWT is for its simplicity, that'll be lost as soon as you want a strong revokation scheme and are required to build your own. Just know what you're getting into.
In a nutshell and at a high-level, here's what the revokation scheme looks like:
- A distributed database/key-value store that allows publishing changes to subscribers/listeners/watchers/whatever-lingo-it-uses.
- An application that performs authentication using JWTs which can subscribe/listen/watch/etc the distributed store on a separate thread.
- An in-memory data structure (hashmap) that the application can use to locally store and look-up blacklisted JWTs.
Engineers should already understand how this all connects. But just to detail it out:
- The distributed store holds blacklisted JWTs. An admin should be able to update it easily, and the changes should propagate to all replications of that store (e.g. globally) extremely fast.
- Applications that do authentication with JWT will write some logic to start a separate thread upon start-up which simply subscribes/listens/watches on the appropriate channel/key/etc and each time a message comes in, runs logic to update an in-memory data structure such as a hashmap to mirror the database store's state of blacklisted JWTs. The action of subscribing/listening/watching happens through a persistent TCP connection - you don't have to make a request every second to detect updates, you get "pushed" the update by the distributed store.
- Authentication logic is modified so that on every request that requires authentication using JWTs, the in-memory data structure is queried to find if the request's token matches or not - this should be super fast, because the data structure is on the application's heap just like many other variables it'd work with while serving the request. If there's a match, we execute request rejection logic.
Revokation (or even reallowance) happens at the time the admin commits the update to the distributed store, so that's basically as fast as you can get as long as your revokation scheme is human-guided.
The obvious downside though is the added complexity, which is why this is a bit troublesome. But as alluded to earlier, this is something that can be offered as a central service by some cloud provider or similar, with client libraries that could require 1 or 2 lines of logic to get going. There may be some authentication-focused businesses who may already be offering this.
Top comments (1)
Thank you for these thoughts on the revocation of JWTs, we've had a recent conversation about JWTs and when / why they are used elsewhere here on DEV, please feel free to dive in there too! 😄