Deep DivesFAPI 2.0

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.

ControlAttack it killsMechanism
Sender-constrained tokens (DPoP or mTLS)Stolen token replayedToken bound to a key only the real client holds. Details →
PKCE (S256)Authorization-code interceptionClient commits to a secret up front; only it can redeem the code.
PARFront-channel request tamperingRequest pushed to the back channel first; browser carries only a handle.
Strong client authClient impersonation, leaked secretsAsymmetric signature or client certificate — no shared secret on the wire.
Code flow only (no implicit)Tokens leaking via the URLTokens only ever issued at the back-channel token endpoint.
iss parameterMix-up across authorization serversResponse 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.