Prerequisite: Familiarity with the concepts introduced in Part 5 — Iso 20022 Payment Gateways. Review it first if the terminology in this part is unfamiliar.

Answer-first: Financial-grade API (FAPI) 2.0 enforces cryptographic API security using Mutual TLS (mTLS), pushed authorization requests (PAR), and signed request objects (JAR/JARM). This prevents credential hijacking, session sniffing, and token forgery in open banking networks. Implementing this architecture enforces sub-50ms P99 latency guarantees, strict component isolation, and automated observability pipelines required for production-grade enterprise operations.

Series (Part 6 of 8): After analyzing the payment data flow in Part 5, this article focuses on the API security layer — where a single design flaw can lead to token theft and unauthorized fund transfers.

What is FAPI 2.0 DPoP Implementation?

FAPI 2.0 DPoP (Demonstrating Proof-of-Possession) binds access tokens to client public keys, preventing stolen token replay attacks in open banking.

The Financial-grade API (FAPI) 2.0 Security Profile standardizes modern OAuth 2.1 cryptographic controls for high-risk financial transactions. Under legacy OAuth 2.0 implementations, bearer tokens could be intercepted from HTTP headers or proxy logs and immediately replayed by malicious actors. FAPI 2.0 eliminates bearer token vulnerabilities by mandating sender-constrained tokens via application-layer DPoP (RFC 9449) or transport-layer mTLS (RFC 8705).

Deploying mTLS within Kubernetes service meshes introduces an initial handshake overhead of 1-3ms, but persistent HTTP/2 connection pooling reduces ongoing per-request overhead to <0.1ms. Application-layer DPoP allows mobile and web clients to cryptographically sign every API request using asymmetric ES256 key pairs without managing X.509 client certificates. Modern regulatory open banking frameworks—including UK Open Banking Read/Write 3.1.10, EU PSD3 draft guidelines, and Australia CDR—require these sender-constrained token architectures to eliminate unauthorized fund transfers and credential interception.


Why Aren’t OAuth 2.0 Bearer Tokens Enough for Fintech?

OAuth 2.0 bearer tokens are vulnerable to man-in-the-middle interception; FAPI 2.0 requires cryptographically sender-constrained tokens.

Bearer tokens have a fundamental vulnerability: anyone holding the token can use it — just like cash. If an attacker intercepts a bearer token:

  1. Replay attack: They can use the token to call APIs at any time during its lifetime.
  2. Token theft: Stealing from memory, logs, or network sniffing → usable until it expires.

FAPI 2.0 solves this using Sender-Constrained Tokens:

MechanismPrincipleProtects Against
DPoP (Demonstrating Proof-of-Possession)The token is bound to a public key. The client must prove private key ownership on every request.Token theft — a stolen token is useless without the private key.
mTLS (Mutual TLS)The token is bound to the client certificate thumbprint. The server verifies the cert.Man-in-the-middle, token theft.
PAR (Pushed Authorization Requests)Authorization parameters are sent directly to the AS, not via the URL (preventing parameter injection).Authorization code injection.
JARM (JWT Secured Authorization Response)The response from the AS is signed → tamper-proof.Parameter tampering.

DPoP: The Mathematical Mechanism

DPoP mechanisms generate asymmetric key pairs to sign HTTP request headers, ensuring only the key holder can use the access token.

DPoP works by requiring the client to sign a proof JWT for every HTTP request, binding it to:

  • The client’s public key (in the JWT header).
  • The specific HTTP method and URI.
  • A timestamp (iat) and a unique jti.
  • The access token hash (ath) — preventing token injection attacks.

DPoP JWT Structure

Structural representation details the decoded header and payload layout of a FAPI 2.0 compliant DPoP proof JWT. Notice the embedded client public key (jwk) in the header and the SHA-256 access token hash (ath) binding in the payload.

Header (base64url):
{
  "alg": "ES256",       // Elliptic Curve P-256 — required by FAPI
  "typ": "dpop+jwt",    // DPoP-specific type
  "jwk": {              // Client's PUBLIC key (embedded in header)
    "kty": "EC",
    "crv": "P-256",
    "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
    "y": "x_tB227t7480s4889c16g60_6n9tq2e624p8r7u"
  }
}

Payload (base64url):
{
  "jti": "unique-uuid-prevents-replay",  // Must be unique per request
  "htm": "POST",                          // HTTP method
  "htu": "https://api.bank.vn/transfers", // URI (no query string)
  "iat": 1718689200,                      // Issued at (Unix timestamp)
  "ath": "base64url(SHA256(access_token))" // Access token hash
}

Signature: ES256(privateKey, base64(header) + "." + base64(payload))

Node.js DPoP Implementation (ES256)

The JavaScript implementation below demonstrates how a mobile or web client generates an ES256-signed DPoP proof header for each outgoing HTTP request. The function constructs the required claims, computes the access token hash, and signs the payload using elliptic curve cryptography.

const crypto = require('crypto');

/**
 * generateDPoPProof — Generates a DPoP proof JWT for every HTTP request
 * @param {string} privateKeyPem - EC private key PEM
 * @param {Object} publicKeyJwk  - Public key JWK (embedded in header)
 * @param {string} httpMethod    - 'GET', 'POST', etc.
 * @param {string} httpUri       - Full URI (no query params)
 * @param {string|null} accessToken - Access token to generate the ath claim
 * @returns {string} DPoP proof JWT
 */
function generateDPoPProof(privateKeyPem, publicKeyJwk, httpMethod, httpUri, accessToken = null) {
    const header = {
        alg: 'ES256',
        typ: 'dpop+jwt',
        jwk: publicKeyJwk  // Embedded public key
    };

    const payload = {
        jti: crypto.randomUUID(),           // Unique per request — prevents replay
        htm: httpMethod.toUpperCase(),
        htu: httpUri.split('?')[0],         // Strip query params
        iat: Math.floor(Date.now() / 1000)  // Current Unix timestamp
    };

    // Bind DPoP proof with access token (if available)
    if (accessToken) {
        const hash = crypto.createHash('sha256')
            .update(accessToken, 'ascii')
            .digest();
        payload.ath = hash.toString('base64url');
    }

    // Encode header and payload
    const base64Header  = Buffer.from(JSON.stringify(header)).toString('base64url');
    const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64url');
    const signingInput  = `${base64Header}.${base64Payload}`;

    // Sign with EC private key
    const sign = crypto.createSign('SHA256');
    sign.update(signingInput);
    const signature = sign.sign(privateKeyPem, 'base64url');

    return `${signingInput}.${signature}`;
}

// Usage:
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
    namedCurve: 'P-256',
    publicKeyEncoding: { type: 'spki', format: 'pem' },
    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

// Convert public key to JWK
const publicKeyJwk = crypto.createPublicKey(publicKey).export({ format: 'jwk' });

// Generate proof for each request
const accessToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNvcmUgQmFua2luZyIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';  // Token from authorization server
const dpopProof = generateDPoPProof(
    privateKey,
    publicKeyJwk,
    'POST',
    'https://api.bank.vn/v1/transfers',
    accessToken
);

// HTTP Request headers:
// DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJqdGkiOiJmOGM5YjEyMyIsImh0bSI6IlBPU1QiLCJodHQiOiJodHRwczovL2FwaS5iYW5rLnZuL3RyYW5zZmVyIiwiaWF0IjoxNTE2MjM5MDIyfQ.SampleSignatureString
// Authorization: DPoP eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNvcmUgQmFua2luZyIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Go DPoP Verification (Server-side)

On the server side, the API Gateway or Resource Server must cryptographically validate every incoming DPoP proof header. The Go verification snippet below enforces algorithm checks, JTI uniqueness against replay attacks, time window limits, and token binding.

package dpop

import (
    "crypto/sha256"
    "encoding/base64"
    "errors"
    "time"
    
    "github.com/lestrrat-go/jwx/v2/jwa"
    "github.com/lestrrat-go/jwx/v2/jwk"
    "github.com/lestrrat-go/jwx/v2/jwt"
)

type DPoPVerifier struct {
    // Track used JTIs to prevent replay attacks
    usedJTIs *RecentJTICache // sliding window cache, e.g. 5 minutes
}

func (v *DPoPVerifier) VerifyDPoP(
    dpopHeader string,
    method string,
    uri string,
    accessToken string,
) error {
    // 1. Parse JWT and extract JWK from header
    token, err := jwt.ParseString(dpopHeader,
        jwt.WithValidate(false), // Manual validation below
    )
    if err != nil {
        return errors.New("invalid dpop jwt")
    }

    // 2. Verify alg is ES256 or RS256 (FAPI forbids weak algs)
    rawAlg, _ := token.Get("alg")
    if alg, ok := rawAlg.(string); !ok || (alg != "ES256" && alg != "RS256") {
        return errors.New("unsupported algorithm")
    }

    // 3. Verify typ = "dpop+jwt"
    if token.JwtID() == "" {
        return errors.New("missing jti")
    }

    // 4. Check replay: jti must not have been used
    if v.usedJTIs.Contains(token.JwtID()) {
        return errors.New("dpop replay detected: jti already used")
    }

    // 5. Verify iat is not older than 30 seconds (prevent delayed replay)
    issuedAt, _ := token.Get(jwt.IssuedAtKey)
    if t, ok := issuedAt.(time.Time); ok {
        if time.Since(t) > 30*time.Second {
            return errors.New("dpop token expired")
        }
    }

    // 6. Verify htm and htu match the actual request
    if htm, _ := token.Get("htm"); htm != method {
        return errors.New("dpop htm mismatch")
    }
    if htu, _ := token.Get("htu"); htu != uri {
        return errors.New("dpop htu mismatch")
    }

    // 7. Verify ath = SHA256(access_token)
    hash := sha256.Sum256([]byte(accessToken))
    expectedAth := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(hash[:])
    if ath, _ := token.Get("ath"); ath != expectedAth {
        return errors.New("dpop ath mismatch: token binding invalid")
    }

    // 8. Verify signature with JWK embedded in header
    // (extracted via jwt.ParseString with key extraction)
    
    // 9. Mark JTI as used
    v.usedJTIs.Add(token.JwtID(), 30*time.Second)

    return nil
}

FAPI 2.0 Mandatory Parameters

FAPI 2.0 mandates PKCE, DPoP or mTLS token binding, Pushed Authorization Requests (PAR), and short-lived access token lifespans.

Source: OpenID FAPI 2.0 Profile

Entropy Requirements

  • nonce: Minimum 128 bits of entropy (≥16 random characters from a secure PRNG).
  • state: Minimum 128 bits of entropy — must be verified when receiving the authorization response.

PKCE Requirements

  • code_challenge_method must be S256. Never use plain.
  • code_verifier must be 43-128 characters from [A-Z a-z 0-9 -._~].

Client Authentication (Token Endpoint)

Evaluating client authentication methods at the token endpoint is critical to ensuring compliance with FAPI 2.0 profile requirements. The comparison below summarizes the cryptographic mechanisms and security levels associated with supported and non-compliant authentication strategies.

MethodMechanismSecurity Level
private_key_jwtClient signs a JWT assertion with a private key✅ FAPI compliant
tls_client_authmTLS with a client certificate✅ FAPI compliant
client_secret_basicHTTP Basic Auth❌ Not allowed in FAPI
client_secret_postSecret in POST body❌ Not allowed in FAPI

mTLS Latency: Kubernetes Benchmark

Benchmarking mutual TLS (mTLS) shows connection pooling reduces handshake overhead from 3ms down to under 0.1ms per gRPC call.

Source: Linkerd Performance Benchmarks.

Optimizing TLS 1.3 handshake parameters in Kubernetes ingress controllers significantly mitigates connection setup overhead and preserves high throughput for microservice communication. The following benchmark highlights the latency impact across initial handshakes, session reuse, and certificate revocation checks.

ScenarioLatency OverheadNotes
Initial mTLS handshake1-3msCertificate exchange + key negotiation
Subsequent requests (Keep-Alive)<0.1msSession reuse, no re-handshake
Expired session (reconnect)1-3msFull handshake again
OCSP stapling+0.5-1msReal-time certificate revocation check

Kubernetes mTLS with Linkerd sidecar:

The YAML manifest below configures Linkerd sidecar injection in a Kubernetes pod to enable automated mutual TLS encryption across service boundaries without altering application code.

# Linkerd annotation to automatically enable mTLS
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-api
  annotations:
    linkerd.io/inject: enabled  # Linkerd sidecar injection
spec:
  template:
    metadata:
      annotations:
        linkerd.io/inject: enabled
        config.linkerd.io/proxy-cpu-request: "100m"
        config.linkerd.io/proxy-memory-request: "20Mi"
    # All traffic between pods will be automatically mTLS encrypted
    # No changes required in the application code

With connection pooling (production recommendation):

To maintain low-latency connections in production environments, the Go HTTP transport code below establishes an mTLS connection pool with TLS 1.3 Keep-Alive parameters.

// Go HTTP client with mTLS and connection pool
tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{clientCert},
    RootCAs:      caPool,
    MinVersion:   tls.VersionTLS13,
}

transport := &http.Transport{
    TLSClientConfig: tlsConfig,
    MaxIdleConns:       100,  // Pool 100 idle connections
    MaxIdleConnsPerHost: 10,  // 10 per host
    IdleConnTimeout:    90 * time.Second,
    // Keep-Alive: subsequent requests do not need to re-handshake → <0.1ms overhead
}

client := &http.Client{
    Transport: transport,
    Timeout:   5 * time.Second,
}

PAR (Pushed Authorization Requests)

Pushed Authorization Requests (PAR) move authorization parameters from front-channel URL query strings to secure back-channel POST requests.

In traditional OAuth 2.0, authorization parameters are sent via URL redirect:

In standard OAuth 2.0 flows, authorization parameters are exposed directly in browser query strings, introducing severe security risks. The code block below illustrates the insecure query string pattern.

❌ Insecure:
https://auth.bank.vn/authorize?
  response_type=code&
  client_id=core-banking-payment-gateway-client&
  redirect_uri=https://app.bank.vn/callback&
  scope=transfer:write&
  client_id=core-banking-payment-gateway-client&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Areq-7f89d3a1
  
→ The URL can be logged, shared, or tampered with via referrer headers

PAR pushes parameters directly to the Authorization Server endpoint:

The Pushed Authorization Requests (PAR) specification mitigates parameter tampering by forcing the client to send parameters via an authenticated back-channel POST request. The sequence below demonstrates the two-step PAR authorization protocol.

✅ PAR Flow:

Step 1: Client POSTs parameters to the AS /par endpoint (authenticated)
POST /par
Authorization: DPoP eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNvcmUgQmFua2luZyIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJqdGkiOiJmOGM5YjEyMyIsImh0bSI6IlBPU1QiLCJodHQiOiJodHRwczovL2FwaS5iYW5rLnZuL3RyYW5zZmVyIiwiaWF0IjoxNTE2MjM5MDIyfQ.SampleSignatureString
Body: response_type=code&scope=transfer%3Awrite&client_id=core-banking-payment-gateway-client&redirect_uri=https%3A%2F%2Fapi.bank.com%2Fcallback

Response: { "request_uri": "urn:ietf:params:oauth:request_uri:random", "expires_in": 60 }

Step 2: Client redirects the user with ONLY the request_uri
https://auth.bank.vn/authorize?
  client_id=core-banking-payment-gateway-client&
  request_uri=urn:ietf:params:oauth:request_uri:random
  
→ No sensitive parameters exposed in the URL

QA & SDET Testing Strategy

Testing FAPI 2.0 security requires attempting replay attacks with intercepted tokens and verifying mTLS certificate validation.

Test 1: DPoP Token Replay Attack Simulation

SDET security test suites must verify that the Resource Server rejects replayed DPoP proof tokens. The Go unit test below simulates a duplicate request using an identical JTI identifier to assert that a 401 Unauthorized status is returned.

func TestDPoPTokenReplayAttack(t *testing.T) {
    // Get valid access token and DPoP proof
    accessToken, dpopProof := getValidTokenAndProof("POST", "/v1/transfers")
    
    // Request 1: Valid → Success
    resp1 := makeRequest(accessToken, dpopProof, "POST", "/v1/transfers")
    assert.Equal(t, 200, resp1.StatusCode)
    
    // Request 2: Use SAME dpopProof (replay attack)
    resp2 := makeRequest(accessToken, dpopProof, "POST", "/v1/transfers")
    
    // Must be rejected: jti has already been used
    assert.Equal(t, 401, resp2.StatusCode)
    assert.Contains(t, resp2.Body, "dpop_replay_detected")
}

Test 2: Key Thumbprint Mismatch (Stolen Token)

To test token binding enforcement, the test suite attempts to use a valid access token paired with a DPoP proof signed by an unauthorized key pair. The test below asserts that the API gateway correctly identifies key mismatch and denies access.

func TestStolenTokenWithWrongKey(t *testing.T) {
    // Get valid token (bound to key pair A)
    accessToken := getAccessToken(keyPairA)
    
    // Attacker possesses the access token but does not have private key A
    // Generate DPoP proof with key pair B (attacker's key)
    attackerProof := generateDPoPProof(keyPairB.Private, keyPairB.Public,
        "POST", "/v1/transfers", accessToken)
    
    // Request with stolen token + wrong key
    resp := makeRequest(accessToken, attackerProof, "POST", "/v1/transfers")
    
    // Must be rejected: thumbprint mismatch
    assert.Equal(t, 401, resp.StatusCode)
    assert.Contains(t, resp.Body, "dpop_key_mismatch")
}

Test 3: mTLS Certificate Expiry

Mutual TLS validation must strictly reject client certificates that have passed their expiration date or failed revocation checks. The script snippet below generates an expired test certificate and verifies TLS handshake failure.

# Generate expired certificate for testing
openssl req -x509 -nodes -days -1 -newkey rsa:2048 \
  -keyout expired.key -out expired.crt \
  -subj "/CN=test-client"

# Test with expired cert
curl --cert expired.crt --key expired.key \
  https://api.bank.vn/v1/transfers

# Expectation: TLS handshake failure, connection rejected
# Server response: 400 Bad Request or TLS alert

FAPI 2.0 Security Checklist

The FAPI security checklist covers DPoP signature verification, mTLS certificate binding, PAR endpoints, and token revocation APIs.

The following pre-deployment security gate checklist outlines the mandatory cryptographic and protocol assertions required before approving API gateway deployments into production.

## Pre-deployment Security Gate

Pre-deployment security gates run automated SAST and FAPI conformance test suites before approving API gateway releases.

### Authorization Server
- [ ] PAR endpoint enabled and enforced
- [ ] JARM (signed response) enabled
- [ ] nonce and state entropy ≥ 128 bits
- [ ] PKCE: S256 only, plain forbidden

### Client Authentication
- [ ] private_key_jwt or tls_client_auth only
- [ ] client_secret_* not allowed

### DPoP Verification (Resource Server)
- [ ] Verify alg ∈ {ES256, RS256, PS256}
- [ ] Verify typ = "dpop+jwt"
- [ ] Verify jti is not reused (replay prevention)
- [ ] Verify iat is within 30 seconds
- [ ] Verify htm = actual HTTP method
- [ ] Verify htu = actual URI
- [ ] Verify ath = SHA256(access_token)
- [ ] Verify signature with embedded JWK

### mTLS (if used)
- [ ] TLS 1.3 minimum
- [ ] Certificate pinning for known clients
- [ ] OCSP stapling enabled
- [ ] Connection pool for latency optimization

💡 Read more: Streaming Fraud Detection — Fraud detection using a FAPI-secured event stream.

mTLS Certificate Pinning and Client Credentials Lifecycle Management in FAPI 2.0

Financial-Grade API (FAPI) 2.0 security relies on Mutual TLS (mTLS) for secure client authentication. This requires the client application to present a valid X.509 certificate during the TLS handshake. The resource server validates the certificate chain against a trusted root certificate authority (CA) and extracts the certificate thumbprint to bind it to the issued access token.

Operationalizing FAPI 2.0 introduces certificate lifecycle challenges:

  • Certificate Rotation: Client certificates expire and must be rotated without interrupting API services. The authorization server supports dual-certificate registration, allowing clients to register a new certificate before the old one expires. During the rotation window, the gateway accepts either certificate.
  • Certificate Pinning: To prevent man-in-the-middle (MitM) attacks caused by compromised external certificate authorities, clients use certificate pinning, hardcoding the expected server certificate thumbprint in the client application.
  • Cryptographic Token Binding Verification: When the client calls a resource API (e.g., retrieving bank account balances), the gateway validates the access token signature and extracts the cnf (confirmation) claim containing the certificate thumbprint. It then compares this thumbprint against the client certificate presented in the active mTLS session. If they do not match, the gateway rejects the request with a 401 Unauthorized error.

DPoP (Demonstrating Proof-of-Possession) token security

To complement mTLS, FAPI 2.0 architectures deploy DPoP (Demonstrating Proof-of-Possession) at the application layer. DPoP binds access tokens to a client-generated private key. When calling a protected API, the client signs a token request with its private key, producing a DPoP proof JWT. The resource server verifies this JWT before granting access. This ensures that even if an access token is leaked or intercepted, it cannot be used by an attacker without the corresponding private key.

Kubernetes Network Isolation for Security Enclaves

At the network layer, FAPI 2.0 workloads run in isolated Kubernetes namespaces with strict network policies. All incoming traffic to the banking core must pass through a specialized API Gateway pod. Direct pod-to-pod communication between external APIs and internal database nodes is explicitly blocked, ensuring that a compromised API gateway cannot directly access ledger databases.

Client Secret Rotation and Token Revocation Mechanisms

Authorization servers implement dynamic client registration (DCR) and automated client secret rotation. If a client application detects a potential credential leak, it initiates an API call to revoke active tokens and rotate keys. The revocation event is streamed instantly to API gateways, which immediately invalidate the associated access tokens.

Cryptographic Auditing and Signature Logs

To meet regulatory requirements for financial auditing, the gateway logs the signature verification results and cryptographic thumbprints of all incoming transactions. These security logs are stored in an immutable, write-once-read-many (WORM) storage system, providing a secure audit trail for forensic investigators.

FAQ

FAPI 2.0 protects financial APIs against token theft by combining mTLS transport security with DPoP cryptographic proof-of-possession.

DPoP or mTLS — which should I choose?

Selecting between DPoP and mTLS depends primarily on the architecture of the consuming client. DPoP is optimal for single-page applications and native mobile apps because it operates at the application layer without requiring client certificate provisioning. Conversely, mTLS is ideal for server-to-server B2B integrations where X.509 certificate lifecycle management can be fully automated. FAPI 2.0 explicitly supports both mechanisms, allowing enterprise financial gateways to accept either sender-constrained token implementation.

Does mTLS affect Kubernetes auto-scaling?

Yes, but a service mesh like Linkerd or Istio handles cert rotation automatically. When a new pod spins up, the sidecar automatically negotiates an mTLS cert from the control plane — this is completely transparent to the application code.

Where should the DPoP private key be stored in a mobile app?

iOS: Secure Enclave (hardware-backed key storage). Android: StrongBox or Android Keystore (hardware-backed when supported by the device). The private key must never be exported out of the secure enclave.

mTLS Client Certificates, Signed Request Objects, and PAR Flow Mechanics

mTLS client certificates and signed JWT request objects establish mutual cryptographic identity between fintech apps and open banking gateways.

Financial-grade API (FAPI) 2.0 provides advanced security controls for banking API networks, protecting transactions from credential hijacking and message alteration.

mTLS Client Certificate Validation

FAPI 2.0 requires Mutual TLS (mTLS) for authentication and token binding:

  • Token Binding: The authorization server binds the issued access token to the client’s mTLS certificate thumbprint (x5t#S256).
  • Resource Server Validation: When the client calls a banking API, the resource server extracts the certificate thumbprint and verifies it matches the bound token. Even if an attacker steals the token, it is unusable without the matching private key.

Signed Request Objects (JAR/JARM)

JWT Secured Authorization Requests (JAR) enforce cryptographic integrity:

  • Signed Requests: Clients sign authorization parameters in a JWT using RSA/ECDSA keys before submitting them, preventing tampering in transit.
  • Signed Responses (JARM): The authorization server returns signed response parameters, preventing authorization code injection attacks.

Pushed Authorization Requests (PAR) Flow Mechanics

PAR prevents authorization parameters from leaking through browser histories:

The step-by-step sequence block below illustrates how PAR separates authorization parameter delivery from browser URL redirection to prevent query string tampering.

1. Client sends authorization parameters in a secure POST to the /par endpoint.
2. Authorization Server validates parameters, generates a reference URI, and returns it.
3. Client redirects the browser to the /authorize endpoint using only the reference URI.

This prevents interception of sensitive query parameters and ensures strict parameter validation before browser interaction begins.

Up Next: Part 7 — Streaming Fraud Detection — Apache Flink CEP patterns, RocksDB memory tuning, async ML inference, and achieving <100ms fraud scoring SLAs.


🔗 Next Step: Continue to Part 7 — Streaming Fraud Detection for the following module in the series.