Skip to content

JWT Claim Rules

UAE Open Finance requires two distinct signed JWTs in every authorization flow. Their claim rules differ in ways that are easy to confuse. This page is the single authoritative reference for both.

JWTSent toPurpose
Request Object (JAR)/par as request=Carries all authorization parameters in a tamper-proof, signed envelope
Client Assertion/par and /token as client_assertion=Proves your application's identity to the Authorization Server — replaces a client secret

Same endpoint, two different JWTs

Both JWTs are sent to /par in the same request, but they serve entirely separate purposes. Mixing up their claim rules — particularly jti and sub — is the most common source of 400 Bad Request errors.

JOSE Header (both JWTs)

json
{
  "alg": "PS256",
  "kid": "<your-signing-key-id>"
}
FieldRule
algMust be PS256 — the only algorithm accepted in the UAE Open Finance FAPI profile
kidThe thumbprint of your signing certificate, as registered in the Trust Framework. The Authorization Server uses this to fetch your public key and verify the signature
typOptional. Not required by the profile

No algorithm flexibility

ES256, RS256, and HS256 are not accepted. Any JWT signed with a non-PS256 algorithm will be rejected.

Request Object (JAR)

The Request Object is a signed JWT whose payload contains all /par authorization parameters. It is sent as the request form parameter.

Claims

ClaimTypeRequiredRuleExample
audstringThe Authorization Server's issuer value — from the LFI's .well-known/openid-configuration. This is not the token endpoint URL.https://auth1.altareq1.sandbox.apihub.openfinance.ae
issstringYour application's client_id from the Trust Frameworka1b2c3d4-5678-...
client_idstringSame value as issa1b2c3d4-5678-...
iatnumberIssued At Unix timestamp — when the JWT was created1713196113
expnumberUnix timestamp. Must be no more than 10 minutes after nbf. Recommended: nbf + 300 (5 minutes)1713196423
nbfnumberUnix timestamp. Must be no more than 10 minutes in the past at the time the Authorization Server processes the request. Recommended: iat - 101713196103
response_typestringMust be codecode
scopestringSpace-separated OAuth 2.0 scopes — see Scopespayments openid
redirect_uristringMust exactly match a URI registered in the Trust Frameworkhttps://yourapp.com/callback
noncestringA fresh UUID for every request. Bound to the ID token — prevents ID token replayf47ac10b-58cc-...
statestringA fresh UUID for every request. Returned in the redirect — prevents CSRFe5f6g7h8-...
code_challengestringBase64url-encoded SHA-256 hash of your code_verifier (PKCE)E9Melhoa2Ow...
code_challenge_methodstringMust be S256 — only PKCE method supportedS256
authorization_detailsarrayRAR object describing the consent — see Consent API Guide[{...}]
max_agenumberMaximum authentication age in seconds. Capped at 36003600
jtistringNot required in the Request Object. Use nonce for replay prevention instead

aud — issuer, not token endpoint

The aud claim must be the Authorization Server's issuer identifier, not the token endpoint URL.

✅  aud: "https://auth1.altareq1.sandbox.apihub.openfinance.ae"
❌  aud: "https://auth1.altareq1.sandbox.apihub.openfinance.ae/token"
❌  aud: "https://auth1.altareq1.sandbox.apihub.openfinance.ae/par"

Find the correct value from the LFI's .well-known/openid-configuration under the issuer key.

Lifetime window

nbf ──────────────────────── exp
 │        max 10 minutes       │
 │   recommended 5 minutes     │

The Authorization Server checks that:

  • nbf is no more than 10 minutes in the past
  • exp is no more than 10 minutes after nbf
  • The current time falls between nbf and exp

Clock skew between your server and the Authorization Server can cause rejections. Always set nbf slightly in the past (iat - 10) to absorb up to 10 seconds of drift.

Client Assertion (private_key_jwt)

The Client Assertion proves your application's identity to the Authorization Server. It is sent as client_assertion with client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer.

It must be sent to both /par and /token. A fresh assertion with a new jti must be generated for every request — the same assertion cannot be reused.

Claims

ClaimTypeRequiredRuleExample
audstringThe Authorization Server's issuer value — same source as the Request Object aud. Not the token endpoint URLhttps://auth1.altareq1.sandbox.apihub.openfinance.ae
issstringYour application's client_ida1b2c3d4-5678-...
substringYour application's client_idmust equal issa1b2c3d4-5678-...
iatnumberUnix timestamp of issuance1713196113
expnumberUnix timestamp. Keep short-lived — maximum 5 minutes after iat. Recommended: iat + 3001713196413
jtistringA unique identifier (UUID) for this specific assertion. The Authorization Server records seen jti values and will reject any reuse. Generate a fresh UUID for every requestc770aef3-6784-...
nbfnumberNot Before. If provided, set to iat - 10 to absorb clock skew1713196103

sub must equal iss

✅  iss: "a1b2c3d4-...", sub: "a1b2c3d4-..."   (same client_id)
❌  iss: "a1b2c3d4-...", sub: ""               (empty)
❌  sub omitted entirely

jti replay prevention

The jti must be a UUID that has never been used before with this Authorization Server. Reusing a jti — even from seconds earlier — will result in a 400 or 401 rejection.

✅  jti: crypto.randomUUID()    ← fresh UUID every time
❌  jti: "fixed-string"         ← rejected on second use
❌  jti omitted

Lifetime window

iat ────────────── exp
 │   max 5 minutes  │

Unlike the Request Object, there is no nbf requirement for the Client Assertion, but keeping exp short-lived (5 minutes maximum) limits exposure if intercepted.

Side-by-side comparison

ClaimRequest ObjectClient Assertion
audAS issuer URLAS issuer URL
issclient_idclient_id
subNot usedclient_id (must equal iss)
exp max10 min after nbf5 min after iat
nbfRequiredOptional (recommended)
jtiNot requiredRequired — must be unique per request
nonceRequiredNot used
stateRequiredNot used
client_idRequiredNot used

Common rejection causes

ErrorLikely cause
aud rejectedaud set to token endpoint URL instead of issuer
jti already usedClient Assertion jti reused across requests
sub missingClient Assertion sent without sub claim
Token expiredexp too short or clock skew — set nbf to iat - 10
Wrong algorithmNon-PS256 algorithm used in JOSE header
kid not foundSigning certificate not yet registered or wrong kid value