DPoP: demonstrating proof of possession
Bearer tokens have one major weakness: stolen tokens grant access from anywhere. DPoP (RFC 9449) binds an access token to a client-held key pair. Every API request carries a fresh DPoP-proof JWT signed by the client's private key and bound to the request's URL, method, and token hash.
Mechanism:
- Client generates an ephemeral key pair per session.
- Token request includes a DPoP proof. Authorization server binds the issued access token to the client's public key (in the token's
cnfclaim). - Every protected API call includes a fresh DPoP proof. Resource server verifies the proof, the binding, and the token together.
Stolen tokens are useless without the private key. Modern API gateways increasingly support DPoP for high-value tokens.
JAR and JARM
JAR (JWT-Secured Authorization Request, RFC 9101): the authorization request itself is a signed JWT instead of URL parameters. Prevents tampering, removes parameter-pollution attacks on the redirect URL, and lets the AS validate the client's identity at request time.
JARM (JWT-Secured Authorization Response Mode): the authorization response (the code or token returned to the client) is a signed JWT instead of plain URL parameters. Prevents response-mode attacks and ensures the client receives an unmodified response.
Both are required by FAPI for high-assurance use cases.
FAPI 2.0 baseline
The Financial-grade API profile is a hardened OAuth 2.0 baseline used in open banking (UK, Brazil, Australia). FAPI 2.0 baseline mandates:
- PKCE for every flow.
- JAR for requests, JARM for responses.
- mTLS or private_key_jwt for client authentication. No client secrets.
- Sender-constrained access tokens (mTLS-bound or DPoP).
- Pushed Authorization Requests (PAR): the client pushes the auth request to the AS via a direct call before redirecting the user.
- No password-grant flow, no implicit flow.
Even outside banking, FAPI 2.0 is the modern reference for "OAuth done right" for high-value APIs.
mTLS-bound tokens
Alternative to DPoP: bind the access token to a client X.509 certificate presented at the TLS layer. The AS includes the certificate's SHA-256 thumbprint in the token's cnf.x5t#S256 claim. The resource server checks that the TLS client certificate hash matches the token's claim.
Comparison with DPoP:
- mTLS-bound: works at the TLS layer; transparent to application code; requires the entire path to be TLS with client auth.
- DPoP: works at the HTTP layer; transparent to TLS; can survive TLS-terminating proxies.
FAPI 2.0 baseline allows either.
RAR: Rich Authorization Requests
RFC 9396. Instead of coarse OAuth scopes ("read", "write"), the client requests an array of structured authorization details. Example: "transfer $500 from account X to account Y for purpose Z." The AS shows the user the exact transaction at consent time and binds the token to those details.
RAR replaces the scope-explosion problem that real-world APIs hit: trying to express "the user authorized this specific payment, not arbitrary payments" via scope strings does not scale.
Where the boundary moves
The modern OAuth/OIDC stack has converged on:
- Confidential clients use mTLS or private_key_jwt; no shared secrets.
- Public clients use PKCE always; tokens bound via DPoP.
- Tokens are short-lived; rotation with family-revocation on reuse.
- Requests and responses signed (JAR/JARM) for high-assurance flows.
- Authorization expressed as RAR for transactional consent.
This is roughly the FAPI 2.0 stack. For new high-value services, designing toward it from day one beats retrofitting.