On Sun, Apr 26, 2020 at 8:46 AM Adam Johnson <m...@adamj.eu
> James, I too would like to know your criticisms! I've always understood that they aren't much different to signed cookies, but I haven't looked too deeply at them.
Well, people asked. So.
The short summary is: JWT is over-complex, puts too much power in the
attacker's hands, has too many configuration knobs, and makes poor
cryptographic choices. This is why we see vulnerabilities in JWT
libraries and JWT-using systems again and again and again.
And even if you get every single bit of it right, in the ideal perfect
world with the ideal perfect implementation, the payoff is you've put
in a ton of work to get something that already existed: signed
cookies, for the use case of session identification, or any of several
better token or token-like systems -- everything from PASETO to just
timestamped HMAC'd values -- for the use case of inter-service
The longer version goes more like this...
JWT is a more complex thing than many people appreciate. In fact, it's
at least *five* things, each specified in its own RFC, plus some more
you have to know about and implement if you want any hope of getting
the actually-sorta-secure version. And right off the bat, that's
worrying: the more different things you have to implement, and the
more places you have to look to find out what and how to implement,
the more opportunities there are to make mistakes.
This complexity comes from the sheer number of different options JWT
tries to support, which in turn is an anti-pattern. JWTs may be
signed, or they may not. They may be encrypted, or they may not. There
are multiple different options for how to sign, how to encrypt, how to
manage and specify keys... in a well-designed system there would be
far fewer options. Ideally, there'd be only one option, and the
solution for a vulnerability being found in it would be to increment
the version of the underlying spec, and change to something
Anyway. In JWT, signature and encryption schemes are effectively
negotiable, which is yet another anti-pattern: you are putting
enormous power in the hands of attackers to negotiate you down to a
bad, or nonexistent, cipher/algorithm for encryption or signing. TLS/SSL
learned this lesson the hard way; JWT has chosen not to learn it at
all. Worse, JWT embeds the negotiation about how to handle the token
into the token itself. This is just asking for trouble, and in fact
trouble has routinely occurred. As a somewhat sarcastic author of my
acquaintance put it:
> It is extraordinarily easy to screw up JWT. JWT is a JSON format
> where you have to parse and interpret a JSON document to figure out
> how to decrypt and authenticate a JSON document. It has revived bugs
> we thought long dead, like “repurposing asymmetric public keys as
> symmetric private keys”.
More succinctly: JWTs inherently and unavoidably violate the
Cryptographic Doom Principle
JWTs put full control of the violation in the hands of the attacker,
who -- thanks to the high level of configurability JWT offers -- is
free to find the set of options most likely to compromise you, and
foist them on you.
That quoted line above about revived long-dead bugs is no joke,
incidentally. Here's one from a few years back:
Five different libraries, written in three different languages, all
had the same bug. I once had the misfortune of arguing with someone
who attributed this to the libraries being built by bad programmers,
or in bad languages. But I personally tend to agree with a line from
> Now you can say look those are all implementation vulnerabilities,
> it's not actually JWTs fault, I disagree. If I can find the same
> vulnerability in five popular libraries, then maybe the spec did a
> bad job of making sure people avoided that vulnerability.
So to completely head off the "it's just bad implementations"
argument, let's turn to the specs themselves: RFCs 7515, 7516, 7517,
7518, and 7519 are the core specifications that make up with is
commonly called "JWT" (and in RFC-land is more properly called "JOSE"
-- "JSON Object Signing and Encryption", of which the token format is
but one part).
The JOSE RFCs make a number of choices regarding default sets of
standardized cryptographic options. For example, RFC 7518 standardizes
thirteen signature algorithm options, seventeen encryption key
management options, and six different encryption cipher options.
This is already a problem -- that's an absolutely gigantic
combinatorial space of options! But then it gets worse.
Let's look just at the first table of options in that RFC, which is
for signing algorithms. It categorizes them as "Required",
"Recommended+", "Recommended", or "Optional". It then places into its
"Recommended" category options like RSASSA-PKCS-v1_5, which was known
to be vulnerable to Bleichenbacher's attack nine years before this RFC
was written (and Bleichenbacher's attack on PKCS#1 in general had been
known for 17 years at that point).
And then if we read a bit further we find ECDSA with P-256 is in the
"Recommended+" category, which the RFC helpfully says means that it is
likely to be bumped up to "Required" in the future. The P-256 curve
is... controversial, to say the least, and it took several additional
years before RFC 8037 finally standardized a better alternative
And that's just one quick glance at one table of one set of the
available options in one of the five core RFCs.
Here's another set of critical vulnerabilities:
Five different libraries, for three different languages (and a
*different* set of libraries and languages than the previous
critical-vulnerability post I linked), all vulnerable to the same
basic invalid-curve attack.
Here's another one from just ten days ago:
Auth0's JWT validation was tricked into accepting unsigned tokens
thanks to a bug in parsing the (attacker-provided and
attacker-selected!) "alg" header parameter.
I could go on with this, but the general idea I'm trying to get across
is: JWT's issues are not just due to specific implementations being
bad. Several are issues that come with JWT itself, and are baked into
its design and specifications. The existence of vulnerabilities in
popular libraries is thus an expected consequence; no amount of
brilliance on the part of library authors can correct for the inherent
problems with JWT.
Meanwhile, we have access to alternatives that are simpler, more
robust, or often both. Django already supports at least two of them
out-of-the-box. As a result, I remain *strongly* against implementing
support for JWTs in Django, in any form, even the draft subset
implementation Claude has posted.