JwksVerifier
in package
Local EdDSA/Ed25519 JWKS verification (CONTRACT.md D-08).
Keys are sourced via OIDC discovery (GET /.well-known/openid-configuration ->
jwks_uri, falling back to {baseUrl}/oauth2/jwks if discovery is unavailable or
omits jwks_uri), TTL-cached, and refetched exactly once when an unknown kid is
encountered before failing closed.
Two security invariants (every sibling SDK independently confirmed both):
- Pitfall 5 / T-alg-confusion: the header
algis pinned toEdDSABEFORE any key lookup is attempted. A token never gets to choose its own verification algorithm. - Pitfall 3 / T-cross-tenant:
GET /oauth2/jwksis organization-wide, not tenant-scoped, so a validly-signed token for a different tenant under the same organization must still be rejected. Thetenant_idclaim is checked AFTER signature verification succeeds.
Deliberately does NOT use firebase/php-jwt's CachedKeySet convenience class — it
requires a PSR-18 client + PSR-17 request factory + PSR-6 cache pool, a dependency
chain D-07 explicitly avoids. This hand-rolled TTL cache mirrors every sibling SDK's
own JWKS-cache shape (e.g. the Python SDK's _jwks.py).
verify() never throws on attacker-controlled token input — malformed/short/
non-3-part tokens, unknown algorithms, unknown kids, and bad signatures all return
null (fail closed). The only thrown exception is the ext-sodium-missing guard,
which is an environment/deployment misconfiguration, not attacker input.
Table of Contents
Methods
- __construct() : mixed
- verify() : array<string, mixed>|null
Methods
__construct()
public
__construct(ClientInterface $http, string $baseUrl[, int $cacheTtlSeconds = 300 ]) : mixed
Parameters
- $http : ClientInterface
- $baseUrl : string
- $cacheTtlSeconds : int = 300
verify()
public
verify(string $jwt, string $expectedTenantId) : array<string, mixed>|null
Parameters
- $jwt : string
- $expectedTenantId : string
Return values
array<string, mixed>|null —Verified claims, or null on any verification failure (never throws on attacker input).