I like the simplicity of JWTs and just used them when implementing an authentication mechanism for an web app where I have an Elixir/Phoenix backend that serves a RESTful JSON API and an Angular 2 frontend.
Today I stumbled about this article (I'm in no way affiliated with this website) and it gave me some doubts about the use of JWT which I would like to discuss. Mainly:
- There is no real way of JWTs to be invalidated without shutting down the whole system. At least when they are stateless.
- That means you cannot revoke access, which could be very bad.
- LocalStorage - where JWTs are mostly stored on the front-end side - is not as secure as Session Storage.
Local storage, unlike cookies, doesn’t send the contents of your data store with every single request. The only way to retrieve data out of local storage is by using JavaScript, which means any attacker supplied JavaScript that passes the Content Security Policy can access and exfiltrate it. Not only that, but JavaScript also doesn’t care or track whether or not the data is sent over HTTPS. As far as JavaScript is concerned, it’s just data and the browser will operate on it like it would any other data.
After all the trouble those engineers went through to make sure nobody is going to make off with our cookie jar, here we are trying to ignore all the fancy tricks they’ve given us. That seems a little backwards to me.
So far I think his points are not valid - as long as one serves everything via https and protects against XSS and CORS attacks. I mean if malicious JS can be executed on the site this is really not a problem of JWT. And JS does not allow mixed content (http and https).
For invalidating all tokens: Just use something like a JWT generation-number variable, that could live in your environment variables and is encoded in every token. It is a simple integer. If you want to invalidate all tokens, increment it. Then you just need a mechanism to check if the generation number matches. This doesn't have to hit the DB and shouldn't be a performance issue. And it shouldn't be too difficult to roll this out over multiple instances with a solid deployment strategy.
For invalidating a single token: Use refresh tokens and a very short lifespan for a JWT (couple of minutes). If the TTL of the JWT is almost over, the user get's a new one via the refresh token. This means a DB hit every couple of minutes.
Or am I mistaken?
You could always store your JWTs within HttpOnly cookies, which will mitigate the risk of them being stolen by any XSS vulnerability.
Of course all the other usual best practices apply - secure flag, HSTS, etc.
Yes, you could do that or even generate new secrets entirely.
So I'm assuming this means using traditional server-side session state mechanisms for your refesh-tokens? This seems a valid approach. Ensure the session tokens are hashed server-side though with a secure algorithm (e.g. SHA-2 - no salt is required).
The only tricky part is clock synchronisation from client to server. If you're expiring them quickly, then any small clock differences may confuse the client and they won't request the refresh token in time. This is making things more complicated, and complexity is the main enemy of security.
If you're expiring tokens so quickly, it would probably make more sense to use traditional server-side session management mechanisms.