601 karma · joined September 28, 2018
blog - https://maxgerber.com
email - max at stytch dot com
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.
> In that case, the signature doesn't solve that attack, because since everything happens in the attacker's browser, the attacker can modify the js locally to disable signature verification.
The frontend doesn't need to verify the ID Token - the backend of the relying part does. The backend can never trust the frontend. The signature is needed so the backend can verify that the ID Token credential was issued by the IDP.
> Just from reading the spec, a reader can wonder, "why is there all this unexplained complexity for no apparent reason?"
Yeah, this is true. Oftentimes the attacks are enumerated at the end of a spec, so it isn't clear when reading an earlier section. For example, the attack we've been discussing is described here: https://openid.net/specs/openid-connect-core-1_0.html#TokenM...
The ID Token can be passed from the Identity Provider to the Relying Party (RP) in a few ways.
When `response_mode=id_token` is used, the ID Token can be passed in the front channel directly to the RP during a browser redirect. Since the ID Token is coming from the browser, it must be signed to ensure that a malicious actor can't tamper with it. Otherwise an actor could swap out a `sub` or an `email` claim and the RP would be none the wiser.
The ID Token can also be returned from the `/token` endpoint after exchanging an authorization code. Since the `/token` endpoint is a back channel call over HTTPS, the ID Token doesn't necessarily need to be signed here to avoid tampering. The RP can trust that TLS gets the job done. However, there are substantial benefits to having it be signed:
- If ID tokens were only signed sometimes, we'd have two different standards for how to construct and handle an ID Token, which would be quite confusing.
- Signed ID Tokens can be passed around to assert identity to other entities within the system. For example, there are some promising draft specifications that explore exchanging ID Tokens for access tokens in other systems. This is only possible because the ID Token cannot be tampered with.
https://datatracker.ietf.org/doc/draft-parecki-oauth-identit...
SAML does not grant any authorization to an upstream API.
Of course, lately anything trivial I ask codex to do - but there is still fun in figuring out what trivial thing I should have it take on next.
- Reading the https://go.dev/blog/errors-are-values blog post (mentioned in the article too!) and really internalizing it. Wrote a moderately popular package around it - https://github.com/stytchauth/sqx
- Becoming OK with sprinkling a little `panic(err)` here and there for truely egregious invalid states. No reason forcing all the parent code to handle nonsense it has no sense in handling, and a well-placed panic or two can remove hundreds of error checks from a codebase. Think - is there a default logger in the ctx?
The drum I'm currently beating is that local MCP is a ton of fun for techies like us - if you're on this website you can `npx ...` or install whatever you want with a modicum of common sense - but local MCP is going to be a dead end for mass adoption. If we want to build MCP servers that get used by everyday people (or on mobile or other locked down ecosystems) then remote MCP + OAuth is the only realistic way forward. I can't get my dad to open up a terminal window - anything over stdio or touching environment variables and API keys is a nonstarter.
- API Keys are much, _much_ easier to use from the command line. CURL with HMAC is finicky at best and turns a one-liner into a big script
- Maintaining N client libraries for your REST API is hard and means you'll likely deprioritize non-mainstream languages. If a customer needs to write their own library to interact with your service, needing to incorporate their own HMAC adds even more friction.
- Tools have gotten much better in recent years- it is much easier to configure a logger to ignore sensitive fields now compared to ~10 years ago
Check out https://aaronparecki.com/2025/05/12/27/enterprise-ready-mcp - there are some great ideas there on how this can be simplified even more in the future.
No MCP on ChatGPT yet, but I can ask chat to generate an output of what we reviewed in a structured format.
In a year from now, Github will run a single public Github MCP server that you will connect to via OAuth - you won't need to install it locally or faff around with tokens or environment variables at all.
The biggest problem is that really good coffee ruins bad coffee foreger. After making my own for so long, traveling and dealing with gas station or hotel breakfast coffee is especially jarring. Sometimes I wish I could forget how good coffee could taste.
https://austen.pages.dev/c2276990-1f2a-40e6-bd26-0cabb55d713...