Hacker News new | past | comments | ask | show | jobs | submit login

Why all the hate for JWTs?

Just pick a crypto scheme and the JWT is just an encoding that makes it easier to use.

(It's just a more convenient way of rolling your own scheme)

That said: random tokens have a lot going for them :)




> Why all the hate for JWTs?

> Just pick a crypto scheme and the JWT is just an encoding that makes it easier to use.

That's not what JWT is, but I can understand why someone would be misled into believing that.

JWT isn't just an encoding format, it also includes a crypto algorithm negotiation protocol that lets the attacker choose the algorithm. Even if you strictly allow-list which algorithm you want to support, you can accidentally bypass this control in many libraries if you support the `kid` (key ID) header. [1]

It also allows attackers to completely strip the security. [2] [3]

Put shortly, JWT is a gun aimed directly at your foot. That's why there's so much hate for JWTs.

[1] https://github.com/firebase/php-jwt/issues/351

[2] https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...

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


> Even if you strictly allow-list which algorithm you want to support

This is the norm.

As for the key ID attack, this sounds like it's just a trick to know where the private key is located? It shouldn't be publicly accessible.

https://blog.pentesteracademy.com/hacking-jwt-tokens-kid-cla...


> Blame the library, or its defaults.

Every JWT proponent says that, but it's a misuse that shows up in multiple libraries, in multiple languages, and isn't explicitly called out in the JWT Best Practices RFC at all.

I'm going to blame the standard for being error-prone.

There's nothing in any JWT RFC, to date, that calls out the need for cryptographic keys to be the raw key material in addition to its parameter choices, rather than just the raw key material. That's a fault of the standard.

That's not a single library's fault. That's the standard's fault.

PASETO has this to say: https://github.com/paseto-standard/paseto-spec/blob/master/d...

> As for the key ID attack, this sounds like it's just a trick to know where the private key is located? It shouldn't be publicly accessible.

This doesn't involve private keys at all. Look at the proof of concept code. https://github.com/firebase/php-jwt/files/6966712/php-jwt-po...


I don't think it's called out in the RFC because when you have a list of all the keys that are accepted, it makes it so the algorithm is effectively whitelisted.


If I give you a map of kid -> key material that looks like this:

  {
      "my-super-cool-key-id": "some hs256 secret key string goes here",
      "another-key-id": "-----BEGIN RSA PUBLIC KEY-----\n ... snip ...",
      "yet-another-key-id": "----BEGIN EC PUBLIC KEY-----\n... snip ..."
  }
And let's say you've got three different API endpoints, which each hard-code a specific algorithm (one HS256, one PS256, one ES256). But, because of the framework you're developing in, you're expected to provide a single configuration object containing a map of key IDs used by the entire application. (This is a common framework quirk.)

What stops you from swapping out the kid in a JWT's header and getting the underlying library to use the wrong key type for the endpoint that accepts HS256?

    {
      "alg": "HS256",
  -   "kid": "my-super-cool-key-id",
  +   "kid": "yet-another-key-id",
      "typ": "JWT"
    }
The answer varies per implementation. The JWT standards do not call this misuse potential out at all.

Strictly listing the keys does not, at all, hard-code the algorithm those keys are used with in every possible programming language and runtime.

Some languages (Java) accidentally prevent this through type safety in the low-level crypto APIs. Others accidentally prevent this by not supporting the kid header.

I have yet to see a JWT library that deliberately prevents this misuse potential. Is that the library's fault? Or their defaults' fault?

EDIT:

The fix, by the way, requires doing this:

  {
      "my-super-cool-key-id": {
          "alg": "HS256",
          "key": "some hs256 secret key string goes here"
      },
      "another-key-id": {
          "alg": "PS256",
          "key": "-----BEGIN RSA PUBLIC KEY-----\n ... snip ..."
      },
      "yet-another-key-id": {
          "alg": "ES256",
          "key": "----BEGIN EC PUBLIC KEY-----\n... snip ..."
      }
  }
And then verifying that the alg for the key matches the alg for the token before attempting to verify the signature/MAC.


> crypto algorithm negotiation

If you control both sides, then you can ignore this part or do it out-of-band.

Though, if you control both sides, then you can use literally anything else too.


But that's the problem with JWTs, the whole "if you..." part. You want fewer of those rather than more in your crypto code, and JWT has too many. That's the whole problem.


There's a lot of material out there explaining what's wrong with JWTs. Two of my recent favourites:

https://groups.google.com/g/django-developers/c/6oS9R2GwO4k/... - on the Django mailing list

https://www.zofrex.com/blog/2020/10/20/alg-none-jwt-nhs-cont... - where the punchline is "Writing the code to sign data with a private key and verify it with a public key would have been easier to get correct than correctly invoking the JWT library. In fact, the iOS app (which gets this right) doesn’t use a JWT library at all, but manages to verify using a public key in fewer lines of code than the Android app takes to incorrectly use a JWT library!"


The main argument is not against JWT, it is against the libraries and that people use them, without knowing what they are doing.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: