* Make sure that you use JSON.parse to handle incoming JWT bodies if you are writing custom JS code. Using any eval() derivative function opens up XSS
* If you include any strings from a user into the body of your JWT, for example as part of a claim, you must output encode and input validate that data to make sure the result is a valid JSON object in which only the claim expected was modified (think of inputs such as "my address', exp:'never', username: 'admin").
* Your server side code must actually check the claims inside your JWTs. This is really an awfully common thing not to do.
* If using access tokens (or other such tokens), know that there are two tokens, an id_token and an access_token. Endpoints may expect both, so that they can use the id_token to obtain a principle and access_token for credentials. It is important that the server code checks that these tokens belong together (at_hash for access_tokens, st_hash for security_tokens). Otherwise the theft of a user id_token implies a malicious user can auth with it even if their access_token is for a different user.
* Look for open redirects in your JWT-application, fiercely enforce CORS, etc policies. Know that session fixation attacks plague OpenID Connect, OAuth, etc.
Always happy to get feedback. Again, this article was written back in 2014, and will be updated.
In regards to your advice about id_tokens and access_tokens. Usually, what I've seen in most attacks is that if a malicious user can get one, they can get both.
A lot of your points though are valid outside of using JWTs. Open redirects vulnerabilities should be plugged and sanitizing user input should be looked at regardless of what you are using in a web application.
Users tend to be looser with their id_tokens (for example posting on forums, stackexchange, etc) than with access_tokens because they understand the access_tokens are credentials whereas the id_tokens are (technically) not.
Definitely open redirects and the session fixation problems are not JWTs specifically - it's just that they tend to plague applications that use JWTs for transport. I admit it's kind of like telling people that talking on the phone while driving is a bad way to use your seatbelt. It's true - but maybe not scoped enough.
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
var decoded = jwt.verify(token, 'shhhhh');
console.log(decoded.foo) // bar
var decoded_unverified = jwt.decode(token); // NOTE: no secret!
console.log(decoded_unverified.foo) // bar
You can decode the token without the secret. Generally JWT is not encrypted and you shouldn't put sensitive info inside. But you can't modify the token without knowing the secret, because you won't have a valid signature.
So I can hand out session tokens, and you can't modify the user ID or the expiration time unless you know the secret.
Just to be a little bit pedantic about the article.
Do not contain any sensitive data in a JWT.
If there's nothing sensitive in the JWT then why might we encrypt it? Sometimes the whole purpose of a JWT is to pass some sensitive auth information whether as a session cookie or an authentication token that a service uses.
If you worried about replay attacks, include a nonce (jti claim), expiration time (exp claim), and creation time (iat claim) in the claims
This isn't always the case... One of the reasons JWT is so powerful is the asymmetric key operations built in to the spec. If some JWT generation and signing body signs a JWT and someone else steals the JWT, all consumer services of the signing service are vulnerable to a replay attack. The JWT is valid for use at ALL consumer services.
If you have symmetric keys and are encrypting/signing a JWT to communicate, and you want to prevent replay attacks with a JTI and EXP, you've lost the stateless property of JWT in order to do it correctly, and you haven't solved the problem of using the JWTs at OTHER services. It only works for 1 to 1 communication.
If you're preventing replay attacks inside of a JWT you've chose the wrong battle and you've decided to design a cryptographic protocol on top of JWTs. Don't pick this fight. Just go home.
The secret signing key should only be accessible by the issuer and the consumer
This won't be the case for asymmetric keys. Services that create JWTs and sign them for other services to verify should not share the private key with other services. This is basic crypto but if someone reads this ONLY this article they may not know.
Thanks, Evan. these are all good points, I'm surprised to see this on HN since it is an old article I wrote.
In regards to the replay attacks, if you are using JWTs in a 3 party setup, and they are validating JWTs locally (not sending them back to a validation endpoint), the jti claim won't be enough.
Thanks again for clearing that up! Considering this blog post may still have a use, I'll update it soon.
I love the idea of JWT but I don't understand how you would use them to store a lot of information, as the token it self would balloon in size. For example using a JWT to authenticate an API request. The JWT would store a user ID and a list of permissions. What if a user has 100 permissions? The token is going to get huge....? Unless I'm completely misunderstanding how to use them.
JWT should not be looked at as a session replacement. For example, you cannot invalidate a JWT token before it's TTL. If you really wanted, you'd need to manage a blacklist of tokens on the server and compare every token against that list which would defeat the purpose of JWT's scalability benefits.
You can, however, come up with other strategies like switching up 100 permissions for 10 permission groups and similar ground-up designs which are JWT-friendly.
> If you really wanted, you'd need to manage a blacklist of tokens on the server and compare every token against that list which would defeat the purpose of JWT's scalability benefits.
The blacklist approach does presumably incur network latency, however, the amount of overhead/space for the blacklist is going to be substantially smaller than the total set of JWTs.
Also: the blacklist only needs to be maintained until a blacklisted token naturally expires, further enabling performance.
Any number of stores or structures would be suitable for the maintaining the blacklist: Memcached, Redis, even a bloom filter (especially if tokens aren't regularly blacklisted) will suffice.
Granted, there are tradeoffs, and at this point, traditional sessions probably make more sense, but it s at least possible.
It's much easier to scale recall from a (likely) small blacklist of token IDs + TTLs than all sessions, everywhere.
Here are some more:
* Make sure that you use JSON.parse to handle incoming JWT bodies if you are writing custom JS code. Using any eval() derivative function opens up XSS
* If you include any strings from a user into the body of your JWT, for example as part of a claim, you must output encode and input validate that data to make sure the result is a valid JSON object in which only the claim expected was modified (think of inputs such as "my address', exp:'never', username: 'admin").
* Your server side code must actually check the claims inside your JWTs. This is really an awfully common thing not to do.
* If using access tokens (or other such tokens), know that there are two tokens, an id_token and an access_token. Endpoints may expect both, so that they can use the id_token to obtain a principle and access_token for credentials. It is important that the server code checks that these tokens belong together (at_hash for access_tokens, st_hash for security_tokens). Otherwise the theft of a user id_token implies a malicious user can auth with it even if their access_token is for a different user.
* Look for open redirects in your JWT-application, fiercely enforce CORS, etc policies. Know that session fixation attacks plague OpenID Connect, OAuth, etc.