IdTokenValidator
in package
ID-token claim validation — CONTRACT.md §12.4, OIDC Core §3.1.3.7.
PURE logic only: no network, no JWT decoding, no crypto beyond a constant-time
compare. The signature half of §12.4 (rules 1–2: alg allowlist, kid lookup,
Ed25519 verification, single JWKS re-fetch) lives in
JwksVerifier::verifyIdTokenSignature() — the SAME verifier the
§10 middleware already uses (§12 forbids forking it). This class holds rules 3–6
(issuer, audience, time, nonce) plus the reason-code vocabulary, so both halves can be
unit-tested independently, mirroring the TypeScript reference's
oidcIdToken.ts/node/jwks.ts split.
Every failure raises AuthError carrying one of the seven stable reason codes
below (CONTRACT.md §12.3 rule 3). Rule 7 (all-or-nothing discard) is enforced by the
caller — AxiamClient::oidcExchange() never returns a token set whose ID token failed
here, so access_token/refresh_token from the same response are dropped with it.
Table of Contents
Constants
- ID_TOKEN_ALG : mixed = 'EdDSA'
- §12.4 rule 1 — the only algorithm this SDK accepts for an ID token.
- MAX_CLOCK_SKEW_SEC : mixed = 60
- Maximum (and default) permitted clock skew in seconds for ID-token time claims.
- REASON_INVALID_ALG : mixed = 'invalid_alg'
- REASON_INVALID_AUDIENCE : mixed = 'invalid_audience'
- REASON_INVALID_ISSUER : mixed = 'invalid_issuer'
- REASON_INVALID_SIGNATURE : mixed = 'invalid_signature'
- REASON_NONCE_MISMATCH : mixed = 'nonce_mismatch'
- REASON_TOKEN_EXPIRED : mixed = 'token_expired'
- REASON_UNKNOWN_KID : mixed = 'unknown_kid'
Methods
- checkClaims() : array<string, mixed>
- §12.4 rules 3–6 — issuer, audience, time and nonce checks over an already-signature-verified claim set. Returns the claims unchanged on success; throws the matching {@see AuthError} reason code on the first failure.
- constantTimeEquals() : bool
- Constant-time string equality, used for the `nonce` comparison §12.4 rule 6 requires. Mirrors {@see \Axiam\Sdk\Amqp\Hmac::verify()}'s use of `hash_equals()`.
- failure() : AuthError
- Build the `AuthError` for a §12.4 failure: a stable machine-readable `$reason` code plus a human-readable message that — per §12.3 rule 3 and §2's construction rules — never embeds the token, a claim value that could carry secret material, or the expected nonce.
- resolveClockSkewSec() : int
- Resolve the effective clock skew: the caller's value clamped into `[0, MAX_CLOCK_SKEW_SEC]`, or the maximum when unset.
Constants
ID_TOKEN_ALG
§12.4 rule 1 — the only algorithm this SDK accepts for an ID token.
public
mixed
ID_TOKEN_ALG
= 'EdDSA'
MAX_CLOCK_SKEW_SEC
Maximum (and default) permitted clock skew in seconds for ID-token time claims.
public
mixed
MAX_CLOCK_SKEW_SEC
= 60
CONTRACT.md §12.4 rule 5 caps this at 60s and forbids any configuration above the bound.
REASON_INVALID_ALG
public
mixed
REASON_INVALID_ALG
= 'invalid_alg'
REASON_INVALID_AUDIENCE
public
mixed
REASON_INVALID_AUDIENCE
= 'invalid_audience'
REASON_INVALID_ISSUER
public
mixed
REASON_INVALID_ISSUER
= 'invalid_issuer'
REASON_INVALID_SIGNATURE
public
mixed
REASON_INVALID_SIGNATURE
= 'invalid_signature'
REASON_NONCE_MISMATCH
public
mixed
REASON_NONCE_MISMATCH
= 'nonce_mismatch'
REASON_TOKEN_EXPIRED
public
mixed
REASON_TOKEN_EXPIRED
= 'token_expired'
REASON_UNKNOWN_KID
public
mixed
REASON_UNKNOWN_KID
= 'unknown_kid'
Methods
checkClaims()
§12.4 rules 3–6 — issuer, audience, time and nonce checks over an already-signature-verified claim set. Returns the claims unchanged on success; throws the matching {@see AuthError} reason code on the first failure.
public
static checkClaims(array<string, mixed> $claims, string $issuer, string $clientId[, string|null $nonce = null ][, int|null $clockSkewSec = null ][, int|null $nowSec = null ]) : array<string, mixed>
Parameters
- $claims : array<string, mixed>
-
The verified JWT payload.
- $issuer : string
-
The discovery document's
issuer(§12.4 rule 3). - $clientId : string
-
The relying party's own
client_id(§12.4 rule 4). - $nonce : string|null = null
-
The nonce to check against, or
nullto skip rule 6 (oidcRefresh/loginClientCredentials). - $clockSkewSec : int|null = null
-
Permitted clock skew in seconds; clamped to self::MAX_CLOCK_SKEW_SEC.
- $nowSec : int|null = null
-
Current time in epoch seconds — injectable so tests can pin it.
Return values
array<string, mixed>constantTimeEquals()
Constant-time string equality, used for the `nonce` comparison §12.4 rule 6 requires. Mirrors {@see \Axiam\Sdk\Amqp\Hmac::verify()}'s use of `hash_equals()`.
public
static constantTimeEquals(string $a, string $b) : bool
Parameters
- $a : string
- $b : string
Return values
boolfailure()
Build the `AuthError` for a §12.4 failure: a stable machine-readable `$reason` code plus a human-readable message that — per §12.3 rule 3 and §2's construction rules — never embeds the token, a claim value that could carry secret material, or the expected nonce.
public
static failure(string $reason, string $message) : AuthError
Parameters
- $reason : string
- $message : string
Return values
AuthErrorresolveClockSkewSec()
Resolve the effective clock skew: the caller's value clamped into `[0, MAX_CLOCK_SKEW_SEC]`, or the maximum when unset.
public
static resolveClockSkewSec(int|null $clockSkewSec) : int
Parameters
- $clockSkewSec : int|null