Skip to main content

FAPI 2.0 Security Profile

Enterprise-grade security for financial APIs, healthcare systems, and high-security applications. FAPI 2.0 adds extra protections on top of standard OAuth 2.0.

When to Use FAPI 2.0

Use FAPI 2.0 for financial-grade APIs, open banking integrations, and any application requiring the highest level of OAuth security.

What is FAPI 2.0?

FAPI stands for Financial-grade API Security Profile. Think of it as OAuth 2.0 with extra locks on the doors. While regular OAuth is secure for most applications, FAPI 2.0 adds additional protections against sophisticated attacks:

Attack TypeFAPI Protection
Token Theft – Attacker steals access tokensDPoP binds tokens to client keys – stolen tokens can't be reused
Request Tampering – Modifying auth requestsPAR sends requests through secure backend channel
Replay Attacks – Reusing old authentication dataNonces and short-lived codes (60 seconds) prevent replay
Code Interception – Capturing authorization codesPKCE S256 ensures only original client can use codes

Key Features

FeatureWhat It Does
PAR (Pushed Authorization Requests)Send auth parameters through a secure backend channel before redirecting the user
DPoP (Demonstrating Proof of Possession)Cryptographically bind tokens to a client's key – prevents token theft
MTLS (Mutual TLS)Client authenticates with a certificate for server-to-server communication
private_key_jwtSign authentication requests with a private key instead of using secrets
Short Auth CodesAuthorization codes expire in 60 seconds (vs. 10 minutes in standard OAuth)

How to Enable FAPI 2.0

Configure FAPI 2.0 settings in the Tenant Portal:

  1. Navigate to OAuth Clients → Select your application
  2. Click the FAPI 2.0 tab
  3. Choose a Security Profile:
ProfileSecurity LevelUse Case
DisabledStandard OAuthRegular web/mobile apps
FAPI 2.0 BaselineHighOpen Banking, PSD2
FAPI 2.0 AdvancedMaximumHigh-value financial services

PAR: Pushed Authorization Requests

Instead of putting all authorization parameters in the URL (where they could be seen or modified), PAR lets you push the request first and get back a short reference to use.

POST /t/\{tenantSlug\}/api/v1/oauth/par

Step 1: Push the Authorization Request

curl -X POST https://app.lumoauth.dev/t/acme-corp/api/v1/oauth/par \
-u "CLIENT_ID:CLIENT_SECRET" \
-d "response_type=code" \
-d "redirect_uri=https://myapp.com/callback" \
-d "scope=openid profile email" \
-d "code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" \
-d "code_challenge_method=S256"

Response

{
"request_uri": "urn:ietf:params:oauth:request_uri:abc123xyz...",
"expires_in": 600
}

Step 2: Redirect with request_uri

curl -X POST https://app.lumoauth.dev/t/acme-corp/api/v1/oauth/token \
-H "DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Li4ufX0..." \
-u "CLIENT_ID:CLIENT_SECRET" \
-d "grant_type=authorization_code" \
-d "code=YOUR_AUTH_CODE" \
-d "code_verifier=YOUR_CODE_VERIFIER"

Response with DPoP Token

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6...",
"token_type": "DPoP",
"expires_in": 3600,
"scope": "openid profile email"
}
token_type is "DPoP", not "Bearer"

When using FAPI with DPoP, the token response returns token_type: "DPoP" instead of "Bearer". Use the DPoP proof mechanism when presenting the token.

DPoP Proof Structure

The DPoP proof is a JWT with these required fields:

FieldLocationDescription
typHeaderMust be dpop+jwt
algHeaderAlgorithm: ES256, RS256, or PS256
jwkHeaderYour public key (never include private key!)
htmPayloadHTTP method (POST, GET)
htuPayloadFull URL of the request
iatPayloadCurrent timestamp (must be within 60 seconds)
jtiPayloadUnique identifier (at least 16 characters)
athPayloadAccess token hash (only for resource requests)

Client Authentication Methods

FAPI 2.0 recommends stronger authentication methods than client secrets. Configure your preferred method in the FAPI 2.0 tab.

MethodSecurityHow It Works
client_secret_basicLowPassword in HTTP Authorization header
client_secret_postLowPassword in POST body
private_key_jwtHigh ✓Sign a JWT with your private key
tls_client_authHigh ✓TLS certificate authentication

private_key_jwt Example

Instead of sending a client_secret, you create a signed JWT assertion:

curl -X POST https://app.lumoauth.dev/t/acme-corp/api/v1/oauth/token \
-d "grant_type=authorization_code" \
-d "code=YOUR_AUTH_CODE" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

The client assertion must contain:

ClaimValue
issYour client_id
subYour client_id (same as iss)
audAuthorization server issuer URL
expExpiration (short – 60 seconds recommended)
jtiUnique identifier

Discovery Document

FAPI 2.0 capabilities are advertised in the OpenID Connect discovery document:

GET /t/\{tenantSlug\}/api/v1/.well-known/openid-configuration

{
"pushed_authorization_request_endpoint": "https://.../oauth/par",
"require_pushed_authorization_requests": false,
"dpop_signing_alg_values_supported": ["ES256", "RS256", "PS256"],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"private_key_jwt",
"tls_client_auth"
],
"authorization_response_iss_parameter_supported": true,
"tls_client_certificate_bound_access_tokens": true
}

Error Responses

ErrorCauseSolution
invalid_request + "PAR required"Client has PAR enabled but didn't use itPush request to /oauth/par first
invalid_dpop_proofDPoP proof is malformed or expiredCheck JWT format, timestamps, and signing
use_dpop_nonceServer requires a nonceInclude the nonce from DPoP-Nonce header
invalid_clientprivate_key_jwt signature failedVerify JWKS is published and keys match

Glossary

TermMeaning
PARPushed Authorization Request – secure way to initiate OAuth flows
DPoPDemonstrating Proof of Possession – binds tokens to client keys
MTLSMutual TLS – two-way certificate authentication
JWKJSON Web Key – format for cryptographic keys
JWKSJSON Web Key Set – collection of JWKs
JKTJWK Thumbprint – hash of a public key for identification
PKCEProof Key for Code Exchange – protects authorization codes

Reference Specifications