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.
That's interesting - why do it this way rather than including a "reusable" signed JWT with the request, like an API token? Why sign the whole request? What does that give you?
Also what made that API so nice? Was this a significant part of it?
Supposedly bearer tokens should be ephemeral, which means either short-lived (say single-digit minutes) or one-time use.
This was supposed to be the way bearer tokens were supposed to be used.
> What does that give you?
Security.
50% of the support issues were because people could not properly sign requests and it caused me to learn how to make http in all sorts of crap to help support them.
Just generate the JWT using, e.g. https://github.com/mike-engel/jwt-cli ? It’s different, and a little harder the first time, but not any kind of ongoing burden.
You can even get Postman to generate them for you: https://learning.postman.com/docs/sending-requests/authoriza..., although I have not bothered with this personally.
Even the very foundational libraries needed to create/sign/handle JWTs in many programming languages are kind of clunky. And I think subconsciously as developers when we encounter clunky (ie high accidental complexity) libraries/apis we sense that the overall project is kind of amateurish, or will take some trial and error to set up properly. Sometimes that's no big deal, but with auth you can't afford to risk your company or product on someone's side project.
For example, in Go, there is really only one major jwt implementation in use [0] and it's the side project of some guy with a day job working on protobufs [1,2]. Also, with all due respect to the contributors because it's a good library considering the level of support it has, it is just not easy to use or get started with.
Part of the problem is also that the JWT specification [3,4] is a bad mix of overly prescriptive and permissive regarding "claims". I actually think it needs to be replaced with something better because it's a serious problem: it adds a bunch of unnecessary fluff to deal with special claims like "email_verified" when that use case could easily just be treated like any other application-specific jwt payload data, AND it then adds a bunch of complexity because almost everything is optional.
Then of course there's the giant problem of handling your own private keys and identity/security infrastructure + all the associated risks. Nothing mature makes that easy, so everybody naturally prefers to delegate it to auth providers. But that tends to make it hard to fully leverage the underlying jwts (eg with custom claims) and might force you into an authorization model/impl that's less flexible than what JWTs actually support, because now you have to use your auth provider's apis.
I think there really needs to be some kind of all-in-one library or tool for developers to operate their own hmac/jwks/authn/authz safely with good enough defaults that typical use cases require ~no configuration. And maybe either a jwtv2 or second spec that strips out the junk from the jwt spec and prescribes basic conventions for authorization. That's actually the only realistic path to fully leveraging jwt for identity and authz, because you couldn't build something like that on top of auth providers' APIs since they're too restricted/disparate (plus the providers are incentivized to sneakily lock you in to their APIs and identity/authz).
Anyway, this is a project I've been toying with for about a year now and we have some funding/time at my company to start tackling it as an open source project. Hit me up if you're interested.
[0] https://github.com/golang-jwt/jwt
[1] https://github.com/golang-jwt
[3] https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
When you "register" the public key with whatever the relying party is, you're also likely going to bind it to some form of identity, so you can't leak this private key to others, either. (And I'm curious, of course, how the relying party comes to trust the public key. That call would seem to require its own form of auth, though we can punt that same as it would be punted for an API key you might download.)
Could you describe how that would work? If two people have the same info, how on earth do you tell which is which?
The post is talking about simplifying things by eliminating all the back and forth. It’s not pretending to invent a secret-less auth system.
In a way this reminds me a bit of SRP, which was an attempt to handle login without the server ever having your password. Which makes me think this is something to be integrated with password managers.
And for the public email providers, a service like Gravatar could exist to host them for you.
Wouldn't that be nice.
and it’s easy to do keypair generation in the browser using subtle crypto. That API doesn’t provide jwk generation, but seems like it would be relatively easy to do even without the jose module. And the browser cab “download” (really just save) the keypair using the Blob API. Keys need not leave the browser.
An api developer portal could just offer - generate a new keypair? - upload your existing public key?
…As a choice. Either way the public key gets registered for your account.
The end. Easy.
https://docs.github.com/en/apps/creating-github-apps/authent...
The JWT website is also super useful https://www.jwt.io/
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.
Just do it right (and at this point it is widely documented what the pitfalls are here), comply with the widely used and commonly supported standards, and follow the principle of the least amount of surprise. Which is kind of important in a world where things need to be cross integrated with each other and where JWTs, JOSE, and associated standards like OpenID connect are basically used by world+dog in a way that is perfectly secure and 100% free of these issues.
Honestly, it's not that hard.
The paradox with Paseto is that if you are smart enough to know what problem it fixes, you shouldn't be having that problem and also be smart enough to know that using "none" as an algorithm is a spectacularly bad idea. You shouldn't need Paseto to fix it if you somehow did anyway. And of course you shouldn't be dealing with the security layer in your product at all if that is at all confusing to you.
PASETO is the “mostly fixed” version of JWTs, but if you’re looking for something with more features, biscuits are quite interesting:
(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!)
[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.
There's a proposal for cross-domain usage via Related Origins, but that scheme depends on the authority of the relying party, meaning you can't say "I'd like to be represented by the same keypair across this set of unrelated domains"
I wish there were something built into browsers that offered a scheme where your pubkey = your identity, but in short there are a lot of issues with that
There's some degree of confusion in your comment. JWKs is a standard to represent cryptographic keys. It is an acronym for JSON Web key set.
> JOSE is a pretty good library (...)
JOSE is a set of standards that form a framework to securely transfer claims.
But this scheme is flexible. You could also have the client send "requested" claims for the server to consider adding if allowed when getting a JWT.
You could also reverse-proxy client requests through your server, adding any claims the server allows.
In that case, the client can possess the JWK keypair and do its own signing.
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.
1. With self-signed JWTs, you could start consuming APIs with free tiers immediately, without first visiting a site and signing up. (I could see this pattern getting traction as it helps remove friction, especially if you want to be able to ask an LLM to use some API).
2. Compare this scheme to something like the Firebase SDK, where there's a separate server-side "admin" sdk. With self-signed JWTs, you just move privileged op invocations to claims – consuming the API is identical whether from the client or server.
3. The authority model is flexible. As long as the logical owner of the resource being accessed is the one signing JWTs, you're good. A database service I'm working on embeds playgrounds into the docs site that use client-generated JWKs to access client-owned DB instances.
I’m yet to see a website that provides an API and doesn’t have a ToS that you have to agree to. Unless you control both parties, or you expose your service only to pre-vetted customers, there is no legal department that is going to allow this.
Right now, user identity is ~based on email, which is in practice very often delegated to a third party like gmail, because most people do not host their own email. So identity = foo@bobsemailservice.cool. To properly validate self-signed JWTs you'd have to instead either host or delegate distinct JWKS endpoints for each identity. Then identity = https://bobsjwkshost.fun/foo. You still have to create and verify an account through a third party, otherwise, your self-signed JWT is just as credible as a self-signed TLS cert.
In both cases the underlying identity model is actually based on DNS, IP, and internet domains - whoever controls those for your email/JWKS controls what gets served by those addresses. So to fully self-host email or JWKS you need to own your own domain and point email/http to IP addresses or other domains. And you need to have something servery running under that IP to handle email/https. That's a big hurdle to setup as is, but even so, all this really does is kick up identity delegation one more notch to the IANA, who decide who owns what domains and IP addresses, by delegating them to registrars and regional Internet registries, with further delegation to AS to actually dole out IP addresses to proles.
I recently started the process of becoming a registrar and securing my own IP addresses and realized THERE ARE STILL MORE LAYERS. Now I have to pay a whole bunch of money and fill out paperwork to prove my business' legal legitimacy and get the stupid magic Internet numbers we call IP addresses. Which then relies on my country's legal and financial systems... And by the way, this part is just completely infeasible for a regular user to actually follow through with (costs are high five digits USD or low six digits USD, IIRC, takes a ton of time).
Because the IANA is a stodgy bureaucratic Important Institution That I Cannot Change, IMO the best way to implement self-hosted auth is by making it as cheap and simple as possible to register a domain and serve email/jwks on it. If we pay them even more money they'll give us a TLD which we should be able to make available for $0.20 (IANA base registration fee because of course) or just eat the cost and make it free pending some other verification process. And then we can set up a portable "serverless" JWKS/SMTP handling tool that people can run.
I've been thinking about this self-hosted identity/auth problem quite a lot and I think there's no ideal solution. If you're fully decentralized and trustless you're probably using "blockchain" and won't have enough throughput, will lock people out of everything online if they lose a key, and will still probably make consumers pay for it to stave off sybil attacks. Also, to use the internet you have to trust the IANA anyway. So just upgrade every authenticated user into their own identity provider and make it extremely cheap and easy to operate, and at long last you can get those precious seconds back by signing up for Internet services with a JWKS path instead of an email address.
Hmm I wonder if you are thinking that the JSON webkeys of the user need to be hosted publicly. I don't think they technically do. It's a common convention to do so (especially at some well-known URL).
But the user actually could instead send their public key on each and every JWT-bearing request they send, and as long as that public key is both (1) pre-registered with the API owner and tied to some identity the API recognizes as some authorized user and (2) can successfully be used to validate the signature, it should work just fine.
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?
From a cursory read, the answer is "it doesn't".
The blogger puts up a strawman argument to complain about secret management and downloading SDKs, but the blogger ends up presenting as a tradeoff the need to manage public and private keys, key generation at the client side, and not to mention services having to ad-hoc secret verification at each request.
This is already a very poor tradeoff, but to this we need to factor in the fact that this is a highly non-standard, ad-hoc auth mechanism.
I recall that OAuth1 had a token generation flow that was similar in the way clients could generate requests on the fly with nonces and client keys. It sucked.
isCornography = url.contains("corno") || url.contains("body") || url.contains("self");
isVirus = url.contains("virus") || competitorUrlRegex.matches(url);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).