I'm happy that it works for you, but you've just been lucky. Statistically speaking, JWT attacks are far more common in the wild than attacks against stateful tokens or other token formats (such as PASETO).

The author of this essay even gave us a few example links[1][2][3]. Most users of JWT didn't go through a security incident, but a large enough percentage did. Far larger than you get with any other method.

> It’s a well-documented standard

I beg to differ at this point. Yes, the base standard itself is well-documented, but a safe way to use it is not. The JWS and JWE specify a great many algorithms without recommending which one of them is safest to use. Some of these algorithms (e.g. most types of RSA in the spec, AES-GCM with KW) are unsafe without extra precaution. The also doesn't mandate any claim (including the "exp" claim!) and does not mandate setting up key ids (which are necessary for key rotation).

If you need to encrypt your token contents the standard also doesn't help you: It just gives examples of how you'd use an unauthenticated encrypted JWT[4] or a "sign-then-encrypt" JWT-in-JWE with RSAES-PKCS1-v1_5[5]. I cannot stress this enough, but BOTH examples in the standards are very obviously insecure. The first example has no signature or authentication at all (i.e. everybody can forge this "encrypted JWT"), while the second example uses a naive sign-then-encrypt scheme with the worst protocol for this type of scheme: RSA PKCS #1 v1.5, which is vulnerable to Bleichenbacher's attack[6].

If you want to use JWT securely (assuming you're ok with 5 minutes access tokens that cannot be revoked and you'll still be maintaining a database for refresh tokens), here is what you need to do to use it safely:

1. Use the HS256 algorithm for monoliths or services behind a single proxy or API pgateway.

2. If you want to distribute authentication across multiple services without making it possible for the various services to generate tokens, use Ed25519 (as per RFC 8037). Unfortunately, this is not supported by many libraries out there. ES256 is not terribly insecure for signing tokens, but there are enough caveats (such as its behavior when the RNG fails) that scare me.

3. Encrypted tokens should be avoided. If you really need them, they can only be used symmetricly (i.e. same way you'd use HS256). They should be done by embedding a JWE payload inside a JWS. The JWE should use alg="A256KW" and enc="AES_256_CBC_HMAC_SHA_512".

4. "exp", "iat" and "jti" claims are mandatory. "jti" should include a 64 byte random value encoded as base64url.

5. "sub", "aud" and "iss" claims are mandatory and should be verified.

6. A "scope" claim containing the tokens scope should be included and verified.

7. A "clid" or "azp" claim containing the Client ID should be included and verified, to identify the client.

8. All keys MUST have a "kid" specified, to allow rotation.

Or you could just use PASETO v4.local/v4.public and get most of that work done within the library itself.

[1] https://nvd.nist.gov/vuln/detail/CVE-2024-22513

[2] https://www.howmanydayssinceajwtalgnonevuln.com/

[3] https://auth0.com/blog/critical-vulnerabilities-in-json-web-...

[4] https://datatracker.ietf.org/doc/html/rfc7519#appendix-A.1

[5] https://datatracker.ietf.org/doc/html/rfc7519#appendix-A.2

[6] https://datatracker.ietf.org/doc/html/rfc8017#section-7.2