If you've already decided to implement JSON Web Tokens (JWT), whether you want JSON Web Encryption (JWE) or JSON Web Signatures (JWS), you should question this decision.
Everything in this blog post was written to be accurate as of RFC 7519, RFC 7515, and RFC 7516. It's possible that new RFCs in the future could supersede the flaws detailed within.
Why You Don't Want JSON Web Tokens
A lot of developers try to use JWT to avoid server-side storage for sessions. This is almost always a terrible mistake and invites developers to come up with clever explanations and workarounds instead of careful engineering.
The two linked posts explain succinctly why this is a bad move, so I won't delve further into the systems architecture issues. There are more pressing issues at stake.
JSON Web Signatures Makes Forgery Trivial
Quoting RFC 7515, section 4.1.1:
The "alg" (algorithm) Header Parameter identifies the cryptographic algorithm used to secure the JWS. The JWS Signature value is not valid if the "alg" value does not represent a supported algorithm or if there is not a key for use with that algorithm associated with the party that digitally signed or MACed the content. "alg" values should either be registered in the IANA "JSON Web Signature and Encryption Algorithms" registry established by [JWA] or be a value that contains a Collision-Resistant Name. The "alg" value is a case- sensitive ASCII string containing a StringOrURI value. This Header Parameter MUST be present and MUST be understood and processed by implementations.
We've seen this bite JWT users before, in the from of critical vulnerabilities in most JWT libraries.
There were two ways to attack a standards-compliant JWT library to achieve trivial token forgery:
- Send a header that specifies the "none" algorithm be used
- Send a header that specifies the "HS256" algorithm when the application normally signs messages with an RSA public key.
This isn't just an implementation bug, this is the result of a failed standard that shouldn't be relied on for security.
JSON Web Encryption is a Foot-Gun
The encryption algorithms permitted by JWE are spelled out in RFC 7518, which comes in two sectons:
- Key encryption, which gives you options such as:
- RSA with PKCS #1v1.5 padding
- RSA with OAEP padding (which has a bogus security proof)
- ECDH over a NIST curve (which are Weierstrass curves which introduce the risk of invalid-curve attacks, unlike Montgomery curves)
- AES-GCM, because no list of questionable public-key encryption modes could be complete without shoehorning a shared-key encryption mode
- Message encryption, which only allows you to choose between AES-CBC or AES-GCM
- This part is OK, assuming you have a solid GCM implementation. Otherwise, you're stuck with CBC+HMAC, which isn't bad but not great.
There is nothing in RFC 7518 that indicates, "non-cryptographers should ever make these selections", which is exactly what's happening.
Wrap-Up
So in conclusion:
- Don't use JWT for sessions
- The JWS standard is completely broken, and total RFC compliance renders your applications vulnerable
- The JWE standard is a minefield that non-cryptographers shouldn't be forced to navigate
What to use instead?
- Sessions: Just use cookies over HTTPS.
- Signatures: Libsodium's
crypto_sign()
andcrypto_sign_open()
. - Encryption: Libsodium's
crypto_secretbox()
andcrypto_box()
APIs (depending on use-case).
Didn't Your Team Audit a JWT Library in 2015?
Yes, we did a free audit for a JWT library in 2015, but do you know what happened since?
- That library added a
None
signer, which reintroduces the risk of one of the critical authentication bypass bugs mentioned above. - The
None
signer was made the default option.
TL;DR
JWT: Not even once.