LFI · Banking · Service Initiation · PII · API Guide

Verifying the TPP JWS Signature 3 min read

This step is optional

The PersonalIdentifiableInformation field is embedded within a request that the API Hub has already verified was signed by the TPP. The outer JWS signature confirms that the PII was submitted by the authenticated TPP and has not been modified in transit.

Verifying the inner JWS signature on the PII payload is therefore not required, but LFIs may choose to implement it as a defence-in-depth measure.

01 When to consider this

Reasons to verify the inner JWS

You may want to verify the TPP's JWS signature on the PII if:

  • Your security policy requires independent verification of all signed payloads, regardless of upstream validation.
  • You want to confirm the specific TPP identity that constructed the PII (available in the iss and sub claims).
  • You are building an audit trail that requires cryptographic proof tied to the TPP's signing key.
02 How to verify

Resolve the JWKS, verify the signature

The inner JWS (the result of decrypting the JWE) is signed by the TPP using their signing key. The JWS header contains the kid of the TPP's signing key, and the iss / sub claims identify the TPP's client_id.

Step 1 — Discover the TPP's JWKS

The TPP's public signing keys are published through the Trust Framework directory. You can resolve the TPP's JWKS URI using the o3-caller-software-statement-id header from the inbound request:

JWKS URItext
https://keystore.directory.openfinance.ae/[software-statement-id]/application.jwks
This resolves active keys only

The URL above returns the TPP's currently active keys. That is sufficient only if you verify at /consent/action/validate or the post consent event, where the signature is seconds old — Pattern A on the decryption guide.

A TPP may rotate its signing key at any time, and a rotated kid leaves the active set. If you verify during the authorisation journey, on /consent/event/patch, or at POST /payments, you MUST fall back to the parallel inactive/ JWKS when the kid is not in the active set. See Verifying the PII Signature — Rotated Keys and the Inactive JWKS.

Pin PS256 yourself

Published JWKS entries carry kty, use, kid and x5c, but no alg. Pin PS256 — the only signing algorithm in the UAE Open Finance FAPI profile — rather than accepting whatever the JWS header proposes.

Step 2 — Verify the JWS

typescript
import { createRemoteJWKSet, jwtVerify } from 'jose'

async function verifyTPPSignature(
  jwsString: string,
  softwareStatementId: string
): Promise<Record<string, unknown>> {
  // 1. Build a JWKS resolver for the TPP's published keys
  const jwksUri = `https://keystore.directory.openfinance.ae/${softwareStatementId}/application.jwks`
  const jwks = createRemoteJWKSet(new URL(jwksUri))

  // 2. Verify the signature and validate standard claims
  const { payload } = await jwtVerify(jwsString, jwks)

  return payload
}

Claims to validate

If you verify the JWS, you SHOULD also validate the following claims:

ClaimValidation
issMust match the TPP's client_id (available in the o3-caller-client-id request header)
subMust match the TPP's client_id
audMust contain your LFI's issuer identifier
expEvaluate against the consent's CreationDateTime, not the current time — see below
nbfEvaluate against the consent's CreationDateTime, not the current time — see below
iatMust be in the past, and consistent with the consent's CreationDateTime
jtiRecord for replay detection if required by your security policy
The timing claims are fixed at consent creation

exp, iat and nbf on the PII JWS are set by the TPP when it signs the PII, and they bound the PAR submission window — not the moment you happen to be verifying. The same PII is replayed unchanged on every payment made under the consent, so on a long-lived consent those claims will normally have lapsed long before execution.

jwtVerify and its equivalents enforce exp and nbf against the current time by default. If you verify anywhere other than consent creation, evaluate them against the consent's CreationDateTime or disable the check — otherwise valid PII will start failing verification as consents age. Whether the consent is still usable today is answered by the API Hub's consent validation on every request, not by a claim inside the PII.