Client Assertion 2 min read
A client assertion is a short-lived, signed JWT that your application presents to the Authorization Server to prove its identity. It takes the place of a static client secret, providing a stronger and more auditable form of client authentication.
Because each assertion is signed with your application's private key, the Authorization Server can verify it using your public key from the Trust Framework — without any shared secret ever leaving your system.
Two endpoints require a client assertion
In UAE Open Finance, a client assertion is required on two endpoints:
A client assertion must be freshly generated for every request. The jti claim (a unique UUID) ensures the Authorization Server can detect and reject replayed assertions.
For a complete per-claim reference — including the exact aud value, jti uniqueness requirements, exp/iat lifetime window, and a side-by-side comparison with the Request Object — see JWT Claim Rules.
JOSE header — algorithm and signing key identifier
The client assertion is a signed JWT composed of a header and a set of claims.
| Field | Value | Description |
|---|---|---|
alg | PS256 | The only algorithm supported by the UAE Open Finance FAPI profile |
kid | string | Key ID of your signing certificate, as registered in the Trust Framework |
Identity, audience, and short-lived timing claims
| Claim | Description | Example |
|---|---|---|
aud | The Authorization Server's issuer URI — obtained from the .well-known discovery endpoint | https://auth.[LFICode].apihub.openfinance.ae |
iss | Your application's client_id from the Trust Framework | a1b2c3d4-... |
sub | Same as iss — your client_id | a1b2c3d4-... |
iat | Unix timestamp of when the JWT was issued | 1713196123 |
exp | Unix timestamp when the JWT expires. Keep this short — 5 minutes is standard | 1713196423 |
jti | A unique identifier (UUID) for this assertion. Prevents replay attacks | f47ac10b-58cc-... |
Set exp to no more than 5 minutes after iat. Long-lived assertions increase the window of exposure if intercepted.
The sandbox provides O3 Utility endpoints that accept your private key and return a ready-made client assertion JWT — useful for confirming your key setup is correct before writing your own signing code. See O3 Sandbox Utilities.
Assemble the claims and sign as a JWS using PS256
Once the header and claims are assembled, sign the JWT as a JWS using the PS256 algorithm and your private signing key.
import { SignJWT, importPKCS8 } from 'jose'
import { readFileSync } from 'node:fs'
import crypto from 'node:crypto'
const ALGORITHM = 'PS256'
const KEY_ID = process.env.SIGNING_KEY_ID! // kid from Trust Framework
const CLIENT_ID = process.env.CLIENT_ID! // your application's client_id
const ISSUER = process.env.AUTHORIZATION_SERVER_ISSUER! // from .well-known
const privateKey = await importPKCS8(
readFileSync('./certificates/signing.key', 'utf8'),
ALGORITHM,
)
export async function buildClientAssertion(): Promise<string> {
const now = Math.floor(Date.now() / 1000)
return new SignJWT({
aud: ISSUER, // Authorization Server's issuer URI
iss: CLIENT_ID, // your client_id
sub: CLIENT_ID, // same as iss
iat: now,
exp: now + 300, // 5-minute expiry
jti: crypto.randomUUID(), // fresh UUID per assertion
})
.setProtectedHeader({ alg: ALGORITHM, kid: KEY_ID })
.sign(privateKey)
} Send the resulting string in the client_assertion form parameter alongside client_assertion_type when calling /par or /token:
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJQUzI1NiIs...See Message Signing (JWS) for the underlying signing helper and additional context on how the signature is produced.
