- Sep 24
FAPI 2.0: A High-Security Profile for OAuth 2.0
- Daniel Krzyczkowski
OAuth 2.0 is the backbone of modern API authorization, but it was designed as a flexible framework rather than a locked-down security standard. It leaves a great many decisions to the implementer: which grant types to allow, how to authenticate clients, whether tokens can be replayed, how to protect the authorization request in transit. For a low-stakes application, that flexibility is a feature. For an API that moves money, exposes medical records, or grants access to government services, every open choice is a potential vulnerability.
This is the gap that FAPI fills. Rather than inventing a new protocol, FAPI is a profile of OAuth 2.0 and OpenID Connect: a tightened, opinionated subset that removes risky options and mandates the strong ones. It began in the world of Open Banking, where regulations such as PSD2 pushed banks to expose account and payment APIs to third parties, and where getting security wrong has immediate financial consequences. Today it has grown well beyond that origin.
What is FAPI?
FAPI originally stood for "Financial-grade API," reflecting where it was born. The name has since been retained more as a brand than a literal scope, because the profile is now positioned as a general-purpose high-security standard. The FAPI 2.0 specification explicitly calls out e-health and e-government as target domains alongside financial services, describing itself as suitable for any API exposing high-value and sensitive data.
FAPI is developed and maintained by the FAPI Working Group of the OpenID Foundation (OIDF), a non-profit international standards body. The specifications go through public review and a formal member vote before reaching Final status.
The first generation, FAPI 1.0, came in two parts: a Baseline profile for read access and an Advanced profile for higher-risk operations such as payments. FAPI 1.0 Advanced was effective but complex, and its design was organized around defending against a specific catalogue of known threats. That approach worked, but it made the profile harder to reason about and harder to prove correct. This is the backdrop against which FAPI 2.0 was designed.
What is FAPI 2.0 and what makes it different
FAPI 2.0 reached Final status on 22 February 2025. It is not merely an incremental patch on FAPI 1.0; it reflects a shift in design philosophy. Four pillars define it.
First, an explicit attacker model. FAPI 2.0 is designed against a published, formally described adversary, defined in a companion document, the FAPI 2.0 Attacker Model. Instead of enumerating individual attacks and bolting on a defense for each, the working group defined the capabilities an attacker is assumed to have and then required the profile to remain secure against that adversary. This produces clearer design guidance and a profile that is easier to analyze.
Second, formal security analysis. The security properties of FAPI 2.0 have been mathematically analyzed and proven to hold under the stated attacker model. Few security standards can make this claim, and it is one of the strongest arguments for adopting the profile. It means the guarantees are not just the opinion of experienced engineers; they have been verified.
Third, alignment with the OAuth 2.0 Security Best Current Practice (RFC 9700). Rather than diverging from the wider OAuth ecosystem, FAPI 2.0 codifies the community's current consensus on secure OAuth.
Fourth, simplicity and interoperability. FAPI 2.0 is deliberately leaner than FAPI 1.0 Advanced. It reduces the number of moving parts, which lowers the chance of implementation error and makes independent implementations more likely to work together.
The attacker model: the foundation of the profile
FAPI 2.0 does not stand alone. It is paired with a companion specification, the FAPI 2.0 Attacker Model, which reached Final status on the same day. Understanding this document is the key to understanding why the security profile makes the choices it does, because every requirement in the profile exists to uphold the goals defined here against the attackers defined here.
The relationship runs in one direction: the attacker model sets out what security must be achieved and against whom, and the security profile is the set of concrete mechanisms derived to achieve it. The two are then tied together by a formal proof, so the guarantees are not just asserted but demonstrated.
Security goals
The attacker model first states, in plain terms, what the profile is trying to protect. There are three goals:
Authorization: no attacker can access protected resources other than their own. Since the access token is the ultimate credential for reaching a resource, this holds as long as no attacker can obtain and use a token for anyone else's resources.
Authentication (when OpenID Connect is used): no attacker can log in at a client under another user's identity. Here the ID token is the credential, so the goal holds as long as no attacker can obtain and use an ID token identifying another user.
Session integrity: no attacker can force a user to be logged in under the attacker's identity, or to unknowingly use the attacker's resources instead of their own. This covers CSRF and session-swapping attacks, the class of problems that
statetraditionally guarded against.
The attackers
Rather than list individual threats, the model defines a set of deliberately powerful attackers and requires the profile to hold against any combination of them, potentially working together. Defining broad capabilities rather than narrow threats is intentional: OAuth and OpenID Connect are complex, new attack variants keep emerging, and enumerating known threats risks missing the next one. By excluding whole categories of attacker capability, the profile aims to rule out attacks that have not even been thought of yet. The attackers are:
A1 - Web attacker. The standard web attacker. It controls its own endpoints, takes part in flows as an ordinary user, tampers with its own messages using tools like proxies and browser dev tools, and sends links that honest users may click. It cannot intercept traffic between other parties or break cryptography.
A1a - Web attacker acting as an authorization server. A variant of A1 that can also participate in the ecosystem as a legitimate authorization server, and so can replay messages it received from honest servers or send users to honest servers' endpoints.
A2 - Network attacker. Controls the whole network (think a rogue Wi-Fi access point), and can intercept, block, and tamper with messages meant for others. It still cannot break cryptography, which is why most attacks unique to it are defended against simply by using TLS.
A3a - Reads the authorization request. Has the web attacker's abilities and can additionally read the authorization request as it travels from the browser to the authorization server. This is realistic: it can happen through browser history, a mobile app registering for a URL, cross-site scripting, or TLS-intercepting antivirus software.
A4 - Reads and tampers with token requests and responses. Models a client that has been pointed at a malicious token endpoint. Notably, this attacker is not relevant to FAPI 2.0, because the profile requires the token endpoint address to be obtained from authoritative metadata over a protected channel. It is retained only for informational continuity with FAPI 1.0.
A5 - Reads resource requests. Has the web attacker's abilities and can additionally read requests after they reach the resource server, for example from TLS-intercepting proxy logs. This is the attacker behind the DPoP proof replay concern discussed in the profile's security considerations.
What the model assumes and excludes
A security model is only meaningful if it is honest about its boundaries, and this one is explicit about what it takes for granted. It assumes that TLS is not broken, that key distribution via JWKS works correctly, that users' browsers and devices are not compromised, and that identity and session management on clients and servers is handled properly. It also assumes secrets are generated well enough that an attacker cannot guess them.
Consequently, several things fall outside its scope: failures in TLS itself, leaked secret keys from human error, compromised databases or remote code execution on servers, compromised user devices, and weak random number generators. It also does not prescribe end-user authentication methods, firewall setup, or internal architecture. These must be assessed separately for each deployment. The model is equally clear that a correct specification does not guarantee a correct implementation, which is why secure development practice remains essential regardless.
Why this matters for the profile
This is what makes FAPI 2.0's central claim, that it is provably secure, more than marketing. The security profile has an accompanying formal analysis that models the profile and proves it meets the goals above against these attackers. Many of the profile's more distinctive requirements trace directly back to a specific attacker: mandatory PAR and the confidentiality of the authorization request answer A3a; issuer identification and metadata-from-an-authoritative-source neutralize the token-endpoint attacker; sender-constrained tokens limit what A5 can do with anything it reads. Read this way, the profile stops looking like an arbitrary checklist and starts reading as a set of answers, each one tied to a threat someone has to defend against
Core concepts of FAPI2 security profile
Before looking at the specific requirements, it helps to understand the building blocks the profile is assembled from. Each is a deliberate choice that closes off a class of attacks.
Confidential clients only. Only clients that can hold credentials securely and authenticate to the authorization server are supported. Public clients (a pure single-page app, or a mobile app with no backend secret) are out of scope, because there is no known way to secure them to the required level.
-
Sender-constrained access tokens. The heart of the profile. An ordinary bearer token is like cash: whoever holds it can spend it, so a stolen token can be replayed. A sender-constrained token is bound to a cryptographic key that only the legitimate client holds, so the token alone is not enough to use it. Two mechanisms are permitted:
MTLS (RFC 8705) binds the token to the client's TLS certificate.
DPoP (RFC 9449) has the client prove possession of a key by signing a fresh JWT per request.
The trade-off: MTLS ties security to the transport layer, while DPoP works at the application layer and is often easier to deploy where inspecting client certificates is awkward.
Strong client authentication. Clients authenticate with either
private_key_jwt(signing an assertion with their private key) or MTLS. Weak options such as a shared secret in a form field are not acceptable.Pushed Authorization Requests (PAR, RFC 9126). Rather than putting authorization parameters in the browser redirect where they can be observed or tampered with, the client pushes them to the authorization server over an authenticated back channel and receives a short-lived
request_uri. The browser is then redirected with onlyclient_idand thatrequest_uri, keeping sensitive parameters off the front channel and guaranteeing their integrity.PKCE with S256 (RFC 7636). Binds the authorization code to the client instance that started the flow, so an intercepted code cannot be redeemed by anyone else. In FAPI 2.0 it also takes over the CSRF protection that
stateused to provide.Issuer identification (RFC 9207). The authorization server returns an
issparameter that the client checks, defending against mix-up attacks where a client is tricked into sending a code or token to the wrong server.Code-only responses. Only
response_type=codeis allowed; no access or ID tokens ever travel through the front channel. This improves both security (less to steal in the browser) and privacy (no identity token exposed there).Grant types. The authorization code flow is primary, with CIBA also supported. Note that only these two flows have been through the detailed formal security analysis; other grants work within the framework but do not carry the same proven guarantees.
The profile: practices and recommendations
The specification organizes its requirements by role, and that structure is preserved here: responsibility for compliance is split among the authorization server, the client, and the resource server, and each team needs to know which rules are theirs.
Network layer protections
Everything rests on a secure transport foundation:
Use TLS 1.2 or later, and follow the IETF's recommendations for secure TLS use (BCP 195).
Validate certificates properly, and use DNSSEC to reduce the risk of rogue domain-validated certificates from DNS spoofing.
For browser-facing endpoints, defend against TLS-stripping downgrades (for example, HSTS with preloading) and do not enable CORS on the authorization endpoint, which is reached by redirect rather than a scripted request.
Server-to-server endpoints that no browser touches have stricter cipher requirements than browser-facing ones.
Requirements for authorization servers
The authorization server carries the largest set of obligations. The most consequential:
Reject the resource owner password credentials grant outright.
Support only confidential clients, authenticated with MTLS or
private_key_jwt.Issue only sender-constrained access tokens, using MTLS or DPoP.
For authorization-endpoint flows: require
response_type=code, mandate PAR (and reject any request that skips it), require PKCE with S256, and return theissparameter.Shrink the attacker's window: authorization codes live at most 60 seconds and are single-use;
request_urireferences expire in under 600 seconds.
Two points deserve a note. First, one requirement is counterintuitive: the profile tells authorization servers not to use refresh token rotation except in extraordinary circumstances. Most OAuth guidance treats rotation as best practice, but with confidential clients and sender-constrained tokens it adds no meaningful security benefit, while introducing real fragility: if a client fails to store or receive the new refresh token, the user's grant is lost with no way to retry. The profile weighs that reliability cost against a benefit already provided by other means, and comes down against rotation.
Second, the specification handles the perennial problem of clock skew by requiring servers to tolerate timestamps a few seconds in the future, so minor clock differences do not cause valid requests to be rejected.
Requirements for clients
Support sender-constrained tokens and strong client authentication (MTLS or DPoP; MTLS or
private_key_jwt).Use PAR and PKCE with S256 for the authorization code flow, generating a fresh PKCE challenge per request, bound to the user agent that started the flow.
Check the
issparameter in the authorization response to prevent mix-up attacks.Obtain the authorization server's metadata from an authoritative source over a secure channel, and confirm the issuer URL matches the
issuervalue in that metadata.Initiate authorization only with the user's consent, and protect that initiation against CSRF so the user is always aware of the context in which a flow began.
Requirements for resource servers
Resource servers guard the actual data. They must:
Accept access tokens only in the HTTP Authorization header, never in query parameters (URLs leak into logs, browser history, and referrer headers).
Verify each token's validity, integrity, expiry, and revocation status.
Confirm the token actually authorizes the requested access.
Verify the sender-constraining, whether via MTLS or DPoP.
Cryptography and secrets
The profile narrows cryptographic choices to safe ones. When creating or processing JWTs, parties must use PS256, ES256, or EdDSA (the Ed25519 variant), and must never use or accept the none algorithm. RSA keys must be at least 2048 bits and elliptic-curve keys at least 224 bits. Machine-handled credentials such as access tokens, refresh tokens, and authorization codes must be generated with at least 128 bits of entropy, so that guessing them is computationally infeasible. For key distribution, the profile favours JWKS URI endpoints, which let parties rotate keys without fragile manual coordination.
Security considerations: the attacks that remain
One of the more valuable parts of the specification is its candour about residual risk. A profile that claimed to eliminate every threat would be untrustworthy. FAPI 2.0 instead documents the attacks that survive its defences, the powerful adversary each one requires, and the mitigations available. Four are worth highlighting.
DPoP proof replay. DPoP does not sign the body or most headers of a request, so an attacker who captures a proof may be able to replay it, potentially with an altered request such as a different payment amount. Mitigations:
Short-lived, server-provided nonces.
Replay prevention using the proof's unique identifier (
jti).Signed requests via FAPI Message Signing.
Choosing MTLS instead of DPoP.
Injection of stolen access tokens (the Cuckoo's Token attack). An attacker who controls a trusted authorization server can bypass sender-constraining by injecting a stolen token into a client. The preconditions are demanding and do not apply to many ecosystems, but where they might, mitigations include:
Using different keys or certificates at each authorization server.
Shortening access token lifetimes.
Authorization request leaks leading to CSRF. An attacker who can read an authorization request and act quickly could substitute their own authorization code and break session integrity. This affects all redirect-based flows in principle. The opportunity can be narrowed by:
Enforcing one-time use of the
request_uri.Allowing only a single code exchange per authorization call.
Keeping authorization code lifetimes short.
Browser-swapping attacks. If an authorization response leaks, an attacker can start a flow in their own browser, lure a victim into completing the authentication, then forward the response back through their own browser to gain access as the user. The specification is clear that no current technology fully prevents this once the response leaks. This is precisely why so many of the profile's requirements exist to keep the authorization response confidential in the first place.
The takeaway across all four: treat the confidentiality of the authorization response as critical, especially when deploying the profile in mobile or other non-standard contexts.
FAPI 1.0 compared with FAPI 2.0
For readers who know the previous generation, the clearest way to understand FAPI 2.0 is by what changed and why:
Design approach: FAPI 1.0 was built on BCM principles (named after the researchers Basin, Cremers, and Meier) and added specific defences against specific known threats. The weakness of that style is that you can never be sure the list of threats is complete. FAPI 2.0 flips the approach: it starts by defining an attacker, an explicit set of capabilities an adversary is assumed to have, states the security goals the profile must uphold against that attacker, and adopts the OAuth Security BCP. This gives clearer design guidance and, crucially, makes the profile something that can be formally proven secure rather than only argued to be secure threat by threat.
Authorization request protection: from JAR (JWT-Secured Authorization Requests) to PAR (Pushed Authorization Requests), for better integrity protection and compatibility.
Response type: from
code id_tokenorcodetocodeonly. There is no ID token in the front channel (a privacy gain), and while clients can skip nonce and signature checks, they cannot skip PKCE (a security gain).Authorization response protection: from JARM to returning only the authorization code. With nothing sensitive left in the response, there is no longer anything to protect (no ID token in the response as
coderesponse is used only).Redirect URIs: In FAPI 1.0, a client's redirect URIs had to be registered with the authorization server in advance. This was a safeguard: if the server only ever redirects to a pre-approved address, an attacker cannot inject their own and steal the response. FAPI 2.0 no longer requires this, because the redirect URI now travels inside the pushed authorization request, a call the client makes directly to the server, authenticated and tamper-proof. Since the server already knows the request genuinely came from the authenticated client and could not have been altered in the browser, it can trust the redirect URI without having seen it beforehand.
CSRF and state protection: In FAPI 1.0, the
stateparameter was the tool for CSRF protection (tying the response the browser receives back to the request it started), ands_hash, a hash ofstatecarried in the ID token, let the client confirm thatstatehad not been tampered with in transit. FAPI 2.0 dropss_hashfor two reasons. PKCE now provides the same CSRF protection by binding the flow's start to the code exchange, sostateis no longer needed for security. And becauses_hashlived in a front-channel ID token, which FAPI 2.0 removes entirely, it had no place to sit anyway. Wherestateis still used (for application purposes), its integrity is partially protected by PAR, since it is sent over the authenticated back channel rather than exposed in the browser redirect.ID token role: from serving as a detached signature to relying on PKCE, so the ID token no longer needs to play that role.
Front-channel ID tokens: from potentially encrypted ID tokens in the front channel to none at all, so no front-channel encryption is required. ID tokens are exchanged only in the back channel.
Request pre-generation: In FAPI 1.0, the authorization request could be sent as a signed JWT (a request object) carrying
nbf("not before") andexp("expires") claims. Those timestamps bounded the window in which the request was valid, stopping an attacker from crafting a request now to use much later. FAPI 2.0 achieves the same time-bounding differently: the client pushes its request to the server and receives a short-livedrequest_uriin return. Because that reference expires quickly, there is no lasting request an attacker could pre-generate and hold onto, so the timestamp claims are no longer needed for this purpose.Custom headers: FAPI 1.0 defined a set of
x-fapi-*HTTP headers (for conveying operational details such as the end user's IP address or an interaction identifier for tracing). These serve deployment and ecosystem needs rather than the core security guarantees of the profile, so FAPI 2.0 moved them out into a separate Implementation and Deployment Advice document. The effect is a leaner core specification focused strictly on what makes the profile secure.Sender-constrained tokens: from MTLS only to MTLS or DPoP, since DPoP is not tied to the TLS layer and can be easier to deploy in some scenarios.
Summary
FAPI 2.0 represents a maturing of high-security API design. By starting from a defined attacker model, submitting the result to formal analysis, and aligning with the broader OAuth security consensus, it offers guarantees that go beyond expert intuition. At the same time it is simpler and more interoperable than the profile it succeeds. Although its name still carries its financial heritage, it is best understood today as a general-purpose profile for any API where the data is valuable enough to be worth protecting properly, whether that is banking, healthcare, government, or beyond. For anyone building or securing such an API, FAPI 2.0 is a strong default worth serious consideration.