FAPI 2.0
One-line relationship: FAPI 2.0 is the security profile — the rulebook. Sender-constrained tokens are one mandatory rule inside it. They’re not two peers; one contains the other.
What it is
FAPI = Financial-grade API, a security profile from the OpenID Foundation that hardens OAuth 2.0 / OpenID Connect to the level a bank needs. It’s what UK Open Banking and EU regulators expect for high-value API access.
FAPI 1.0 shipped as two profiles (Baseline, Advanced). FAPI 2.0 replaced them with a single Security Profile built around an explicit, formally-analysed attacker model — the spec names the attacks it defends against and proves the controls close them. That’s the line to the client: it’s not a checklist assembled by taste, it’s a defined threat model with matching controls.
The attacker model → controls
The spec assumes an attacker who can read the browser front channel, intercept or inject an authorization code, steal a token from logs or a proxy, tamper with request parameters, and run a mix-up across authorization servers. Each control neutralises one or more.
| Control | Attack it kills | Mechanism |
|---|---|---|
| Sender-constrained tokens (DPoP or mTLS) | Stolen token replayed | Token bound to a key only the real client holds. Details → |
| PKCE (S256) | Authorization-code interception | Client commits to a secret up front; only it can redeem the code. |
| PAR | Front-channel request tampering | Request pushed to the back channel first; browser carries only a handle. |
| Strong client auth | Client impersonation, leaked secrets | Asymmetric signature or client certificate — no shared secret on the wire. |
| Code flow only (no implicit) | Tokens leaking via the URL | Tokens only ever issued at the back-channel token endpoint. |
iss parameter | Mix-up across authorization servers | Response names the issuer; client verifies. |
The three pillars, unpacked
PKCE — Proof Key for Code Exchange (RFC 7636)
The client invents a random code_verifier, and sends only its hash — code_challenge = SHA256(verifier), the “S256” method — in the authorization request. At the token exchange it presents the raw verifier; the server hashes it and checks the match. An attacker who intercepts the code in the browser can’t redeem it — they never saw the verifier, and can’t reverse the hash.
Analogy: you show the shape of a padlock up front, and only you hold the key that fits it later.
Try it — generate a real PKCE pair:
PAR — Pushed Authorization Requests (RFC 9126)
Normally every auth parameter (scope, redirect_uri, state, code_challenge) rides in the browser URL — the front channel, visible and tamperable. PAR flips it: the client POSTs the request to the back channel first, authenticated, and gets an opaque request_uri. The browser redirect then carries only client_id + that handle. Nobody can tamper with scope or redirect_uri in transit, and the client is authenticated before the user sees a screen.
Analogy: instead of shouting your full order across a crowded room, you hand it to the clerk directly and get ticket #47; then you just call “47.”
Strong client authentication
The application proves who it is at the token and PAR endpoints, using an asymmetric method — no shared secret transmitted:
private_key_jwt(RFC 7523): the client signs a short JWT assertion with its private key; the AS verifies with the registered public key.tls_client_auth(mTLS, RFC 8705): the client presents an X.509 certificate; the same cert can also bind the tokens, so one mechanism does two jobs.
Analogy: the app signs its name rather than whispering a shared password.
What to take to the client
FAPI 2.0’s selling point is the named attacker model. You’re not asking them to trust a vendor’s checklist — you’re adopting the profile UK/EU banking already expects, whose every control maps to a specific attack. The one real decision it leaves open is DPoP vs mTLS for token binding — covered next.