FAPI 2.0
FAPI 2.0 financial-API compliance — mTLS-bound tokens, ML-DSA signatures, JAR/JARM, sender-constrained tokens.
FAPI 2.0 Final (https://openid.net/specs/fapi-2_0-security-profile-FINAL.html) is the OpenID Foundation's security profile for financial / banking APIs. It composes existing IETF + OAuth standards into a single profile that operators MUST satisfy to interoperate with FAPI 2.0 client deployments. The composition (per §5):
- PAR (Pushed Authorization Requests, RFC 9126) — REQUIRED - PKCE with S256 (RFC 7636) — REQUIRED, PLAIN refused - Sender-constrained tokens via DPoP (RFC 9449) OR mTLS (RFC 8705) — REQUIRED, exactly one - Authorization-server issuer in callback (RFC 9207) — REQUIRED - TLS 1.2+ with FAPI-approved cipher suites (TLS 1.3 default) - JAR (JWT-secured Authorization Request, RFC 9101) when the request-object is signed
The framework already ships every component primitive. FAPI 2.0 conformance is therefore a posture-coordination problem: the operator declares the deployment is FAPI-bound, and the framework asserts every primitive in the chain is configured per the profile. b.auth.oauth.create(...) remains the operator's OAuth declaration; b.fapi2.assertOAuthConfig is the boot-time gate that refuses to start a FAPI-declared deployment if any mandate is missing.
b.fapi2.assertConformance(opts) #
{
senderConstraint: "dpop" | "mtls", // REQUIRED
parRequired: boolean, // default true
pkceMethod: "S256", // S256 only; "plain" is refused
requireIssuerInCallback: boolean, // default true (RFC 9207)
requireJarOnSignedRequests: boolean, // default true (RFC 9101)
}
Inspect operator-declared FAPI 2.0 wiring and return a structured report. Throws Fapi2Error for non-S256 PKCE or absent sender-constraint; non-mandatory mandates report WAIVED. Emits a fapi2.posture_asserted audit event so regulators see a single conformance assertion per boot.
var report = b.fapi2.assertConformance({
senderConstraint: "mtls",
parRequired: true,
pkceMethod: "S256",
});
report.conformant;
// → true
report.findings[0].requirement;
// → "pkce-s256"
b.fapi2.assertOAuthConfig(oauthOpts) #
{
pkce: boolean,
pkceMethod: "S256",
dpop: boolean,
mtls: boolean,
senderConstraint: "dpop" | "mtls",
par: boolean,
}
Boot-time gate over a b.auth.oauth.create(opts) configuration. Throws Fapi2Error when PKCE is disabled or non-S256, when no sender-constraint is declared, when both DPoP and mTLS are set (over-binding ambiguity), or when PAR is disabled. Operators call this immediately after constructing the OAuth client so a misconfigured deployment refuses to start.
try {
b.fapi2.assertOAuthConfig({
pkce: true, pkceMethod: "S256",
mtls: true, par: true,
});
} catch (e) {
// → never reached for the conformant config above
throw e;
}
b.fapi2.posture() #
Returns "fapi-2.0" when b.compliance.set("fapi-2.0") has been called, else null. Convenience for code that branches on the posture without calling b.compliance.current() directly.
b.compliance.set("fapi-2.0");
b.fapi2.posture();
// → "fapi-2.0"
b.fapi2.assertCallback(query, opts?) #
{
{ requireJarm?: boolean } // override (default: derive from posture)
}
Runtime gate the OAuth callback handler invokes BEFORE parseCallback to enforce FAPI 2.0's wire-format invariants against the live response:
- **§5.4.2 iss-callback** — refuse callbacks lacking iss under any FAPI 2.0 posture (regardless of OP discovery). - **§5.3.2 / Message Signing JARM mandate** — under fapi-2.0-message-signing, the OP MUST deliver the authorization response as a signed JWT (response= query param). A bare-param callback is refused.
Returns silently on success. Throws Fapi2Error on any FAPI invariant breach. No-op when no FAPI posture is active.
app.get("/oauth/callback", async function (req, res) {
var query = Object.fromEntries(new URL(req.url, "x:/").searchParams);
b.fapi2.assertCallback(query);
var parsed = await oauth.parseCallback(query);
res.end(JSON.stringify({ code: parsed.code }));
});
b.fapi2.assertAuthzRequest(authzParams) #
Runtime gate the operator wraps around the AuthorizationUrl builder to enforce FAPI 2.0 §5.3.2 — under any FAPI 2.0 posture, the operator MUST send a signed JAR (RFC 9101 request= OR request_uri=). Refuses authorization-request param shapes that look like the bare RFC 6749 query.
var params = { request: signedRequestJwt };
b.fapi2.assertAuthzRequest(params);
var url = oauth.authorizationUrl(params);
Last updated 2026-08-08T16:39:49.652Z by seeder.