Protocols

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.

Use case: If your application accesses bank accounts, patient data, tax records, or any high-value resource under a regulated open API mandate (PSD2, NHS DPCR, eHealth DSI), FAPI 2.0 is required or strongly recommended.

FAPI 2.0 Requirements at a Glance

FeatureFAPI 2.0 BaselineFAPI 2.0 Message Signing
PKCERequiredRequired
PARRequiredRequired
client authprivate_key_jwt or mTLSSame
DPoPRecommendedRequired
JARMRequired
RAR (Rich Auth)OptionalOptional
response_modequery.jwtjwt

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:

bash
# Generate ES256 key pair for client authentication $ openssl ecparam -genkey -name prime256v1 -noout -out client-private.pem $ openssl ec -in client-private.pem -pubout -out client-public.pem
# 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.

TPP Backend ───> Clavex /par  POST (signed JWT + params) → request_uri
TPP Backend ───> Browser  Redirect to /authorize?client_id=...&request_uri=urn:...
Browser ───> Clavex  GET /authorize (no sensitive params in URL)
bash
# 1. Build client assertion JWT (for private_key_jwt auth) # Header: {"alg":"ES256","kid":"my-key-id"} # Claims: {iss,sub,aud,jti,exp} # See examples.html for code in Go/Node/Python
# 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.

bash
# DPoP proof JWT — header must include "typ":"dpop+jwt" and the public key # Claims: {jti, htm (method), htu (URL), iat, ath (access_token hash for resource calls)}
# 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.

bash
# Clavex redirects to: /callback?response=eyJ... # Decode and verify the JARM response JWT $ JARM_PAYLOAD=$(echo "$JARM_JWT" | cut -d. -f2 | base64 -d 2>/dev/null) $ echo $JARM_PAYLOAD | jq . { "iss": "https://id.acme.eu/acme", "aud": "clavex_fapi_...", "exp": 1746889260, "code": "AUTH_CODE", "state": "abc123..." } # Verify signature against https://id.acme.eu/acme/.well-known/jwks.json # Then verify: iss, aud, exp, state matches your session

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:

bash
# In PAR body — describe the exact payment being authorized -d 'authorization_details=[{ "type": "payment_initiation", "locations": ["https://api.bank.example.com"], "instructedAmount": { "currency": "EUR", "amount": "150.00" }, "creditorName": "ACME SRL", "creditorAccount": { "iban": "IT60X0542811101000000123456" } }]'
# 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:

  1. Register at https://www.certification.openid.net
  2. Create a new FAPI 2.0 Baseline test plan pointing to your issuer URL
  3. The suite will register its own clients dynamically (ensure Dynamic Client Registration is enabled)
  4. Run all test modules — each maps to a specific FAPI requirement
Test plan: Use the plan type fapi2-security-profile-id2-test-plan (Baseline) or fapi2-message-signing-id1-test-plan (Message Signing). Clavex passes all modules in both plans.