1. RS256 vs ES256 - you shouldn't use either. RS256 not just an old standard on the way out. With safe keys sizes tokens are often going to be too large and signing too slow for you. ES256 on the other hand, suffers from many theoretical flaws and at least one practical flaw that (complete breakdown if nonce is reused) that helped jailbreak the PS3.
The only reasonable asymmetric signature algorithms implemented for JWT are Ed25519 and Ed448, but they are still not supported by most libraries.
2. Key IDs - always include them, even if you're just using 1 key for now. Otherwise you cannot rotate keys securely without having to reject all existing tokens.
3. Have relatively short lifetimes for JWTs if you're not using a blacklist. A token that lasts for 180 days with no possible way to revoke it is a dangerous little thing.
4. Always verify that you only accept JWTs signed by the algorithm you're expecting. Otherwise you might be fooled by a JWT signed with HMAC-SHA256 by your RSA public key (which is known to the attacker: https://auth0.com/blog/critical-vulnerabilities-in-json-web-...
5. Under no circumstances accept tokens signed with "none". The standard is just bonkers.
6. Rotate keys every month if you can, not every 2 years. This is keeping your joints well-oiled.
7. Damage control is still possible when using JSON Web Signature (JWS). JSON Web Encryption (JWE), on the other hand, is just a train wreck that is very hard t ouse properly. Avoid it all costs. PASETO provides a viable alternative for most cases it makes sense to use an encrypted token.
8. If you can avoid JWT, just avoid it. It's just too hard to implement securely. In the past there weren't any widespread alternatives, but PASETO is supported in most common platforms now and is clearly a better option.
https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
I agree that JWT has all sorts of flexibility that make it hard to use well but NIST curves work just fine.
If you think they are backdoored then sure, Ed25519 is a better option but real world constraints may require you to use a NIST curve for now.
As far as I know, neither of these issues is relevant to their usage with ECDSA (although invalid curve attacks should be a good enough reason to avoid using these curves with ECDH completely), but experience with SHA-1 and RC4 has thought us that algorithms with theoretical problems are likely to be practically broken sooner or later.
But NIST curves are not even the main issue with the ES* algorithms in JWT. The real issue is ECDSA:
1. Verification is slow. P-256 is about 2-4 times slower than Ed25519 [2]. This kind of speed hit may often be unacceptable. 2. Nonce reuse is an issue. The PS3 implementation was extremely bad, but random number generators can often be broken. This is not a theoretical issue - it used to happen verify often with docker containers until recently. There are alternative schemes that allow using ECDSA with a deterministic synthetic nonce [3], but this is not supported by any JWT implementation I know of. Ed25519, on the other hand, uses a synthetic nonce.
[1] https://safecurves.cr.yp.to/ [2] https://bench.cr.yp.to/results-sign.html [3] https://tools.ietf.org/html/rfc6979
There usually is a "don't use JWTs" alternative, but it's almost always shared secrets. Passwords, cookies, or some variation on that concept.
In that light, even poor jwt hygiene is better than none as long as you avoid a few key mistakes.
But on the other hand, security is, at it's core, the assessment of what guarantees a given technology can actually provide versus what you _depend_ on it to provide. So better tech can be worse security if you overestimate its capabilities.
JWT is just a badly designed standard which like many other badly designed standards (XMLDsig, older versions of TLS) can be used safely if you choose the safest subset you can and tread with care.
But there are faster, simpler and more secure replacement for JWT nowadays like PASETO. If you can, you should use them.
I have read the papers and seen the talks -- it seems fairly compelling.
PASETO does seem like a cryptographic secure alternative that addresses the pitfalls of the JOSE standard and has most of the mitigations mentioned in this blog-post (No cryptographic-algorithm agility) and it supports the same functionalities of JWT/JWE and JWS. So I am convinced on getting that standardized, but it also needs XChaCha20-Poly1305 AEAD to be standardized too [0].
Fernet was also around as being a secure alternative, but it has been mostly replaced by Branca [1] and PASETO.v2.
[0] - https://github.com/bikeshedders/xchacha-rfc
[1] - https://branca.io
Or, like with Oauth2, is the idea that JWT gets transmitted back to the server with every request, and the refresh token only when needed and therefore having a slightly smaller risk of intercepting both keys (assuming the refresh token is not stored in cookies)?
You never store refresh tokens in a frontend. You can store access tokens (short-lived) in the frontend but you should never store refresh-tokens.
Instead of using refresh tokens, they have a "silent authentication" mechanism[1]. The idea is: sometime before the user's initial token expires, your app goes through the silent-auth process in an invisible iframe. Assuming the user's authentication credentials are still valid, the invisible iframe will eventually use the browsers postMessage() functionality to deliver a new token to the main app's frame, your app then quietly starts using this new token that has a new cryptoperiod.
The silent-auth mechanism doesn't use any different inputs than a normal Auth0 SSO login. Your app is constantly re-authenticating until the user is not allowed to login any more. This allows you to set short expiration times with no interruption of the user at all.
[1] - https://auth0.com/docs/api-auth/tutorials/silent-authenticat...
(Id argue that JWT is the wrong tool to use in a react front end - store that in the orchestration and implement some strong session management betweem FE and orch through an access gateway)
Alternatively, don't use access token / refresh token but an ID token. The refresh of the token will be done via the ID token and the user's session (cookie) to the OAuth provider.
The JWT server would check to see that the refresh token probably stored in the browser with a cookie or localStorage is valid before sending the new JWT.