I think the problem is that you need to either make the JWT have a short expiration or engineer a way to invalidate them before the expiration if the need to do arises.
From what I can tell most people implement a blacklist of invalid tokens that need to be expired early. This isn't exactly the same as a traditional sessions table since it turns the problem on its head. You're likely to have a lower number of early expired JWTs than valid sessions.
The other way you might be able to solve this is to generate a per-user JWT key and use that to sign the JWT or embed that in the JWT. When you need to force a log out, just regenerate that value for that user.
In our app the JWTs have a 5m lifetime. When a JWT is generated we also generate a single-use refresh token.
When a client tries to use an expired JWT the request fails, and the client will then exchange the refresh token for a new JWT/refresh token pair, and finally retry the request with the new token.
The refresh operation can reject the request if the user has been deactivated (it's basically a new login request, using the expired JWT as the username and the single-use token as the password).
Perhaps I'm misunderstanding, but how does this help in the case of a compromised token? Doesn't this assume the attacker hasn't also compromised the refresh token?
Presumably, the token and refresh token are both stored in the client-side app. If that gets compromised, the attacker now has the username/password combo they need to restore the session after the T+5mins has expired.
Since the refresh-token is single-use, the user will be logged out when trying to refresh their own token, and will presumably then login again, which should invalidate the attacker's refresh token.
But I agree it's not a perfect system. This is meant to specifically address the problems of long-lived tokens, since JWTs are hard to revoke without checking a blacklist on the server-side.
The main problem is that localStorage is more vulnerable to some classes of attacks than secure, http-only cookies.
> I think the problem is that you need to either make the JWT have a short expiration or engineer a way to invalidate them before the expiration if the need to do arises.
That's not a problem. It's literally how they were designed to be used: expiration time and nonces.
From what I can tell most people implement a blacklist of invalid tokens that need to be expired early. This isn't exactly the same as a traditional sessions table since it turns the problem on its head. You're likely to have a lower number of early expired JWTs than valid sessions.
The other way you might be able to solve this is to generate a per-user JWT key and use that to sign the JWT or embed that in the JWT. When you need to force a log out, just regenerate that value for that user.