14 comments

[ 2.1 ms ] story [ 30.6 ms ] thread
This article uses "ES256" for the alg, GitHub uses "RS256" as their alg and a very deranged few use "none".

The point here is this article is giving the developer lots of rope to hang themselves with the JOSE standard on JWT/K/S and it is a sure way to implement it incorrectly and have lots of security issues.

PASETO is a much better alternative to work with: https://paseto.io with none of the downsides of the JOSE standard.

is the author suggesting allowing the client to set their own claims and using that to auth whatever action they are going to take? I have to be misunderstanding what they are saying - that sounds fraught with risk
That site is blocked by Fortinet as "pornography."
> Visit our website. Create an account. Verify your email. Create a project. Add your credit card. Go to settings. Create an API key. Add it to your password manager. Drop it in your .env file. Download our SDK. Import it. Pass your env var in. Never share your API key. Make sure you never commit it to source control.

None of this "BS" actually goes away with self-signed JWTs, right? Just replace mentions of "API Key" with public/private key and it's otherwise a similar process I think.

I feel like I’m not understanding the target audience for this post: are there people/companies out there specifically paying other companies to be their key-holding party for JWT issuance purposes? I know about SSO providers of course, but that’s several layers of abstraction up.

(Maybe my confusion here is that these JWTs are being described as self-signed, as if there’s a JWK PKI cabal out there, like the bad old days of the Web PKI. There isn’t one that I know of!)

Interesting, so instead of OpenAI giving me an API key, I give them a public key, which they register. Sounds like what we already do with GitHub. I like it.
The model here feels not entirely dissimilar to Passkeys? Both are user provided auth tokens??

[Ed: allegations that the following is inaccurate! Probably checks out? Yes I meant the browser not the domain bound part, that seems solid.] Pity that Passkeys are so constrained in practice by browsers, that using them pretty much requires you trust the cloud providers absolutely with all your critical keys.

I _love_ JWTs for API authentication - one of the nicest APIs I ever consumed was essentially JSON RPC over JWTs. Unfortunately they represent a huge usability hit over API Keys for the average joe. Involving cryptography to sign a JWT per request makes an API significantly harder to consume with tools like Postman or CURL. You can no longer have nice click-to-copy snippets in your public docs. You either have an SDK ready to go in your customer's language or ecosystem of choice, or you're asking them to write a bunch of scary security-adjacent code just to get to their first successful request. No, I don't have a JWT library recommendation for Erlang, sorry.

Not that an API couldn't support both API Keys and JWT based authentication, but one is a very established and well understood pattern and one is not. Lowest common denominator API designs are hard to shake.

This is already something in mainstream authentication applications you host yourself on your own domain. We use Keycloak. I don't know why anyone would install a JavaScript library to do this. It's not that difficult.
On the B2B2C section, my mind immediately went to OAuth. For a developer like Bob giving his end users access to a service, wouldn't a standard OAuth flow where his users grant permission to his app would be the more conventional and secure solution?

It feels like that model handles key management, delegation, and revocation in a well-established way.

What am I missing here that makes this a better fit?

what is this drawback???? surely there must be a catch somewhere right??? if its that easy then everyone would love to use this
This neglects a few pretty fundamental problems.

First, it only works for APIs where a user is present. This doesn't work for APIs where your server is the client. Otherwise there's nobody to visit the "payment URL" that the author describes. If you did this on the server, you need to share the keys with all the machines in your fleet (so each machine doesn't get a payment URL when it boots), which feels just like an API key. And if the payment URL could be completed programmatically, it requires the server to provide a value given to you by the API vendor to note your identity as they understand it, which feels exactly like a bearer token.

But this is just OAuth in reverse. Instead of authing and getting a token, you generate a key and then auth. Either way you need to store a sensitive value. It's questionable where there's material benefit to not sending a bearer token over the wire (TLS works well).

Another problem is key management. Where in the flow do I tell the API what my device is? "This is my Pixel 9 Pro" isn't going to be a thing I'd expect to answer when going through the payment URL. So now I've got a public key registered and I lost my phone. I have to log into this API and tell them "yeah don't trust that key anymore." Which one? I suspect the best ux you can do is to just show the last time a key was used, which is unhelpful for users with multiple devices.

The B2B2C note at the bottom is just clown shoes. The servers simply aren't authing to each other (the payment URL has to go to the end user), as I noted above. And if a user needs to auth to every back and API that the app uses, that's kind of wild. Yes, you can do clever things with ZKPs but you can't solve the real UX problems here with more crypto. And that's assuming that all the back end APIs you're using fully support this scheme.

The last problem is an issue with invalidating key material. If I log into the vendor and say "My device was imaged while going through customs, don't trust this public key anymore", the server needs to remember that key forever to know that it's banned. You can't just delete the key. Consider: I mark the key as invalidated, then open my phone and use the app. It sends the bad key, which isn't recognized. I go through the payment URL and re-auth. That key is now active again on both my device and the image that was taken going through customs.

The only way to avoid this is for every key ever used is remembered until the end of time. If someone tries to use an invalid key, a separate response needs to be sent telling them the key is no longer allowed and to create a new one. Without that, the device can't possibly know if the user didn't auth in the payment URL yet, or if they authed and the key was invalidated (and a new one should be created).