FAPI 2.0 Setup
FAPI 2.0 (Financial-grade API Security Profile) is an OpenID Foundation specification
for high-security OAuth 2.0 deployments in open banking, healthcare, and government.
Clavex implements FAPI 2.0 Baseline + Message Signing profiles: PAR, JARM, DPoP,
and private_key_jwt client authentication.
FAPI 2.0 Requirements at a Glance
| Feature | FAPI 2.0 Baseline | FAPI 2.0 Message Signing |
|---|---|---|
| PKCE | Required | Required |
| PAR | Required | Required |
| client auth | private_key_jwt or mTLS | Same |
| DPoP | Recommended | Required |
| JARM | — | Required |
| RAR (Rich Auth) | Optional | Optional |
| response_mode | query.jwt | jwt |
Step 1 — Register a FAPI Client
FAPI clients use private_key_jwt authentication and require a JWKS URL
or inline JWKS for their signing key. Generate an ES256 key pair:
# Host the JWKS at a public URL, e.g. https://app.example.com/.well-known/jwks.json # Register the client $ curl -s -X POST https://id.acme.eu/api/v1/organizations/$ORG_ID/clients \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "client_name": "PSD2 TPP Client", "redirect_uris": ["https://tpp.example.com/callback"], "grant_types": ["authorization_code", "refresh_token", "client_credentials"], "response_types":["code"], "token_endpoint_auth_method": "private_key_jwt", "jwks_uri": "https://tpp.example.com/.well-known/jwks.json", "require_pushed_authorization_requests": true, "require_dpop": true, "authorization_signed_response_alg": "ES256" }' | jq '{client_id}'
Step 2 — PAR (Pushed Authorization Request)
Instead of sending authorization parameters in the browser URL (where they're visible
in logs and referrer headers), PAR POSTs them directly to Clavex and receives a
request_uri that the browser redirects with.
# 2. PAR request — POST authorization parameters server-to-server $ curl -s -X POST https://id.acme.eu/acme/par \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=clavex_fapi_..." \ -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \ -d "client_assertion=$CLIENT_ASSERTION_JWT" \ -d "response_type=code" \ -d "response_mode=jwt" \ -d "scope=openid+payments" \ -d "redirect_uri=https://tpp.example.com/callback" \ -d "code_challenge=$CHALLENGE&code_challenge_method=S256" \ -d "state=$STATE" | jq . { "request_uri": "urn:ietf:params:oauth:request_uri:01925f...", "expires_in": 90 }
# 3. Redirect user to /authorize — only client_id + request_uri in URL https://id.acme.eu/acme/authorize?client_id=clavex_fapi_...&request_uri=urn:ietf:...
Step 3 — DPoP (Demonstration of Proof of Possession)
DPoP binds access tokens to a client key pair, preventing token replay attacks. The client generates a fresh DPoP proof JWT for each request.
# Token exchange with DPoP proof $ curl -s -X POST https://id.acme.eu/acme/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "DPoP: $DPOP_PROOF_JWT" \ -d "grant_type=authorization_code&code=$CODE&..." | jq . { "access_token": "eyJ...", "token_type": "DPoP", // ← not "Bearer" "expires_in": 300 }
# Call resource server — include DPoP proof with ath = SHA-256(access_token) $ curl https://api.bank.example.com/accounts \ -H "Authorization: DPoP $ACCESS_TOKEN" \ -H "DPoP: $DPOP_PROOF_WITH_ATH"
Step 4 — JARM (JWT Secured Authorization Response)
With response_mode=jwt, the authorization response is returned as a
signed JWT, preventing tampering with the code/state in the redirect. Verify it with
the issuer's JWKS before extracting the code.
Step 5 — RAR (Rich Authorization Requests)
For PSD2 / open banking, use RAR to describe the exact resource access being requested
in a structured authorization_details object:
# The access_token will carry the authorization_details claim # the resource server can verify the exact authorized payment
Certification Testing
The OpenID Foundation runs the FAPI Conformance Test Suite online. To run it against your Clavex instance:
- Register at
https://www.certification.openid.net - Create a new FAPI 2.0 Baseline test plan pointing to your issuer URL
- The suite will register its own clients dynamically (ensure Dynamic Client Registration is enabled)
- Run all test modules — each maps to a specific FAPI requirement
fapi2-security-profile-id2-test-plan (Baseline) or
fapi2-message-signing-id1-test-plan (Message Signing).
Clavex passes all modules in both plans.