As I understand it, the typical (and simplified) usage of JWT refresh tokens is as follows:
Thus, when a user accidentally leaks his password out or loses his device, he can simply change his password, which would cause all previously issued refresh tokens to become invalidated.
However, the obvious security hole here is that the short lived access token is still perfectly valid for the next 10 minutes. And 10 minutes, no matter how short a time, is still plenty of time for a malicious user to cause some damage.
The only possible solutions I can think of are to:
maintain a blacklist or whitelist of access tokens. This makes the usage of refresh tokens seem pretty redundant. If we're going to hit the database on every request or to keep a cached list of blacklisted access tokens, then what's the point of having a refresh token?
make the expiry of the access token shorter (e.g: every 1 minute instead of every 10 minute). This doesn't solve the problem perse, it just does some damage control, since it shortens the window of time a malicious user has to do damage. And hitting the database for a new access token every minute doesn't seem much better than hitting the database on every request.
I have been working on the exact same problem. Although I cannot say I am any sort of definitive authority on the subject, I'm glad to share what I came up with based on a lot of research and building out a proof of concept.
The requirement is to have instant token access token revocation. During regular operation of the application, the actual probability of the scenario of a malicious user gaining access to someone's account is relatively low. This is not to say it should not be accounted for, but it's not going to be the case for 99.9% of request that come into your system, thus having it check the access token against the database on all the requests where it doesn't matter is bad design in my opinion.
However, bad design or not, it doesn't change the requirement. Requiring the access token to be refreshed every minute does not seem to be much better as it would put tremendous strain on the auth server and database. Managing an in memory access token revocation list wouldn't do much good because it wouldn't be shared across instances. Depending on your volume of users this may be able to get a way with using a database for while, but I don't think it would scale beyond a certain point.
The solution I chose to go with is using a shared in memory database/cache. I've evaluated Cassandra, Redis, and Apache Ignite and for the time being, decided to use Ignite. Because I am not sure how it will perform once this goes to production, I've made the components easily swappable for another in memory solution in case the performance is not sufficient.
I have a JWT filter responsible for validating each request, at the end of which I make a call to the shared cache to check a access token revocation list. I anticipate the list will be empty the vast majority of the time. To further reduce potential performance degradation, I hash to tokens to about 40 characters using MD5 before revoking them. This capability allows me to have an hour long access token life and a refresh token with an 18 hour life without worry that I won't be able to remove a malicious user if the need should arise.
Personally, I don't see a way around keeping track of some user state on the backend. The trick is to do it in such a way that you can still easily add instances of your backend to scale your application.
The purpose of JWT tokens is that they are self-contained and live on their own for a short while. If that does not suit your requirements you can revert to another type of access token, namely an opaque one that requires so-called introspection at the Authorization Server (AS). You are right that (if the results of that call are not cached) that call will hit the AS every time and partly defeat the purpose of the refresh token but on the other hand, actively having to revoke cached access token results from each Resource Server that has done an introspection call will result in a management and overhead nightmare.
There's no silver bullet. You will have to choose the access token type, expiry and cache duration that matches your situation an security requirements best.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With