FAPI: the Financial-grade API Security Profiles
FAPI is the OpenID Foundation's hardened profile of OAuth 2.0 and OpenID Connect for APIs where a stolen token means a stolen payment. This guide covers FAPI 1.0 Baseline and Advanced, the FAPI 2.0 Security Profile and its attacker model, Message Signing and CIBA — what each one requires, the authorization-code flow message by message, which open-banking ecosystems mandate which profile, and a checklist for shipping it.
Message Signing: non-repudiation
The Security Profile guarantees that nobody but the right client can use a token. It does not let a bank prove, a year later and to a court, that this TPP asked for this payment — TLS gives integrity in transit, not an artefact you can store. FAPI 2.0 Message Signing adds application-level signatures for exactly that, defined as separately certifiable options so an ecosystem can mandate the ones it needs:
| Message | Mechanism | Key rules |
|---|---|---|
| NR1 / NR2 pushed and front-channel authorization requests | JAR (RFC 9101) signed request object, sent to the PAR endpoint | aud is (or contains) the issuer URL; nbf at most 60 min in the past; exp at most 60 min after nbf; typ: oauth-authz-req+jwt. Because the request went through PAR, the front-channel request is covered automatically. |
| NR3 authorization responses | JARM — response_mode=jwt | The whole response (code, state, iss) is one signed JWT; iss lives inside the JWT, not beside it. Encryption of the JARM response is possible but is not what non-repudiation needs. |
| NR4 introspection responses | RFC 9701 JWT introspection responses | The resource server requests application/token-introspection+jwt and verifies the signature — a stored proof of what the AS said about a token. |
| NR5 ID tokens | Already signed under the Security Profile | Clients verify the signature; no new server requirement. |
And the API calls themselves? HTTP Message Signatures
Signing the resource requests and responses — the payment order and the bank's acknowledgement — was split out of Message Signing into a separate FAPI 2.0 HTTP Signing draft built on RFC 9421 HTTP Message Signatures: a Signature-Input header naming the covered components (method, target URI, selected headers, Content-Digest of the body) and a Signature header carrying the signature. It is the same mechanism as Web Bot Auth and Visa's Trusted Agent Protocol in agentic payments — decode one in the HTTP Message Signature decoder. Until ecosystems adopt it, FAPI 1.0 deployments typically rely on JWS-signed payloads (UK Open Banking's x-jws-signature) or eIDAS seals for the same purpose.
mTLS or DPoP?
Both satisfy the sender-constraint requirement. The Security Profile's security considerations are candid about the trade-off:
| mTLS (RFC 8705) | DPoP (RFC 9449) | |
|---|---|---|
| Where the binding lives | TLS layer: the client certificate on the connection. | Application layer: a signed JWT per request. |
| Deployment | Needs a TLS terminator that passes the client certificate to the application; harder behind CDNs and managed load balancers. eIDAS QWACs already exist in PSD2 ecosystems. | Works through any TLS terminator; the client just needs a key pair. |
| What is covered | Everything on the connection — body and all headers. | Method, URI, access-token hash, timestamp, nonce. Not the body or most headers. |
| Attacker A5 (reads processed resource requests) | Cannot reuse the request — no certificate. | Could replay the proof, possibly against an altered body (different amount or payee) within its freshness window. Mitigations: short-lived server nonces, jti replay detection, signed resource requests, or choosing mTLS. |
| Clock sensitivity | None beyond certificate validity. | iat freshness — hence the 10 s / 60 s skew rules and the nonce mechanism. |
Related tools: HTTP Signature Decoder · Base64URL decoder (take a JWT apart) · ASN.1 / DER decoder (client certificates) · SHA-256 (compute ath and x5t#S256)