A standard OAuth deployment has an odd property: the token is the only signed thing in the flow. The request that asks for it travels as query parameters. The response that delivers the code travels as query parameters. The client proves who it is with a password. Each of those is a place where the protocol trusts the transport to do a job the transport was not asked to do.
This release closed those gaps. None of it is novel — it is the shape the Financial-grade API profile has specified for years — but the reasoning behind each piece is worth writing down, because "we implemented FAPI" is not an explanation of anything.
RS256 access tokens: the resource server stops needing a secret
Access tokens used to be signed with HS256, a symmetric algorithm. That works, and it has a structural consequence: every party that needs to verify a token needs the same key that mints one. A resource server that validates tokens is holding a key that can forge them.
With RS256, verification uses the public key from /.well-known/jwks.json. A resource server can check every token we issue and cannot produce a single one.
HS256: verify key == sign key → every verifier is a potential issuer
RS256: verify key is public → verifiers verify, and nothing more
The practical difference shows up the day you onboard a service you do not fully control, or the day a service is compromised. Under the symmetric scheme, that is a key rotation across every integration. Under the asymmetric one, it is not an issuer compromise at all.
private_key_jwt: clients stop having passwords
A confidential client normally authenticates with client_secret — a bearer password, sent on every token request, stored in the configuration of every deployment of that client, and equally usable by anyone who reads it out of a log, an environment dump, or a screenshot.
With private_key_jwt (RFC 7523), the client signs a short-lived assertion with a private key and sends that instead. The key never transits. We hold only the public half, registered directly or fetched from the client's jwks_uri, and clients can register that way through dynamic client registration.
What this stops is credential replay from observation. Reading a client_secret out of a log gives an attacker the credential. Reading a signed assertion out of a log gives them a used, expired, single-purpose artefact.
JAR: the request stops being editable
An authorization request is a URL. Every parameter in it — scope, redirect_uri, state, response_type — is a query parameter that passes through the user's browser, which is to say through software the client does not control, on a device the client does not control, possibly via an intermediary that rewrote it.
JAR (RFC 9101) packs those parameters into a signed request object. We verify the signature before acting on any of it. A parameter that was altered in transit fails verification rather than being honoured.
This matters most for the parameters that people think of as inert. scope is not inert. redirect_uri is very much not inert.
JARM: the response stops being editable too
The symmetric gap, and the one people forget. Having signed the request, the response comes back as bare query parameters — the code, the state, or an error. JARM wraps that response in a signed JWT, so the client can verify that what it received is what we sent.
Signing one direction and not the other is a half-measure that reads as thoroughness. If you do JAR, do JARM.
Algorithm agility, before you need it
Alongside the above: an EC P-256 signing key and ES256 ID tokens, with the ID-token signing algorithm selectable per client, including through dynamic client registration.
The reason to build this before there is a reason to use it is that algorithm migrations are not code changes, they are coordination problems. A deployment that can only sign one way has to move every client at once. A deployment that signs per client can move one client, watch it, and move the next.
Two smaller ones with the same character
DPoP-Nonce (RFC 9449 §8-9), opt-in at the token endpoint. DPoP already binds a token to a key; the nonce challenge additionally makes a proof unusable if it was captured and replayed later, by requiring a server-supplied value the attacker did not have when they captured it.
SCIM ETags (RFC 7644 §3.14) on Users and Groups. Two administrators editing the same user concurrently used to mean last-write-wins, silently. Optimistic concurrency turns that into a failed update the caller can retry, which is the difference between a conflict and a quiet data loss.
What none of this fixes
Worth being clear: signing the request and the response does nothing about a user who approves a consent screen they did not read, and nothing about a client whose own storage is compromised. These changes remove the transport and the shared secret from the trust equation. They do not remove the human, and they do not remove the client.