I'm sure you've seen a lot of articles suggesting to use JWT, and some people who haven't done enough of JWT to say don't use JWT (or maybe some who gave it a try for 5 mins, and thought no, JWT is for noobs)
I'm going to write about how I'm using JWT and while it does feel foolproof, it obviously isn't, (every app has vulnerabilities)
Okay, first things first, I'm going to assume you know what is JWT and how to validate, I don't want to go through what JWT is, every article even remotely touching about JWT will explain what it is.
Using symmetric key complicates things, it requires you to share this key with other services if they also need to validate JWT.
Asymmetric key can simply expose it's public key to public storage (even S3).
When an application starts, it starts a background job which will create a keypair and store public key in public storage, and private key in it's own secure memory (not even in secondary memory, private key should only be stored in RAM)
Which might beg the question, how do we scale?
Actually, this is more scalable, say you're auto scaling, every time one instance of your identity service starts, it'll simply generate a key pair.
When signing any JWT, use the current private key to sign and include
Now at any given time, there might be N number of valid private/public key (N = number of instances of your identity service running), this will increase with scale.
Now since you have the kid, you can fetch the public key for that particular kid and simply validate it. (And of course you can cache it)
By this time, you've done it, you've a scalable JWT based auth server, it rotates keys at given interval, and there's no way for a human to get access to key because it's in RAM (if a hacker gets access to server and somehow looks at RAM, I guess that is still vulnerable, but then we have more things to worry about, I guess we could make it harder, if you have some idea, please comment below)
Every time I talk about JWT with a group of people, there's always a question of logging out.
First off, setting a lower value of expiry is good idea, as short as 15 minutes is a very good point for security already. But we can do better.
I have two approaches, first is simply store the JWTs created by refresh tokens in db, and you can blacklist them when users log out, but this means you're storing a lot of tokens in your db. You can use
jti claim for this.
What I actually like is having a secondary value in JWT
rid this is not a standard claim, but idea is to store which refresh token actually generated this jwt, that way when refresh token is revoked, we simply put this on blacklist for a particular amount of time (once all jwts expire from this refresh token, say in 15 mins, we can clean this blacklist up)
- We're using a auth in a way that other services are able to easily verify if jwt is valid
- key pairs are rotated every N minutes (I do few hours)
- private key never leaves RAM so it's more difficult to generate fraudulent JWTs.
- Logging out can be done using a blacklisting mechanism (pub/sub could be a game changer)
- if your auth server needs to scale, it can do so, and private key doesn't need to be sent to other 9 instances, because there's nothing wrong with having 10 valid key pairs at the same time.
- This is super scalable, say you have bunch of servers across the world, and you could in theory have 10 identity service on each region of the world, let's say 50 identity service running at any time. Even in this case, because you can simply fetch public key using kid, there's no issue, you can easily scale this.