Verifying with JWKS
Identity providers publish their public keys as a JSON Web Key Set (JWKS) at a
well-known URL. The JWK parser turns that document into a map of PublicKeys keyed
by kid, so you can verify a provider’s tokens — and survive key rotation — without
hard-coding anything.
Fetch and parse the key set
JWK::parseKeySet() takes the decoded JWKS array and returns [kid => PublicKey] —
exactly the shape decode() wants:
<?php
use Flytachi\Jwt\JWK;
use Flytachi\Jwt\JWT;
// 1. Fetch the provider's JWKS document
$jwksJson = file_get_contents('https://www.googleapis.com/oauth2/v3/certs');
$jwks = json_decode($jwksJson, true);
// 2. Parse it into [kid => PublicKey]
$publicKeys = JWK::parseKeySet($jwks);
// 3. Verify the ID token — decode() matches the token's kid to a key
$payload = JWT::decode($idToken, $publicKeys);
echo $payload->getClaim('email');Why kid rotation just works
Providers rotate keys and serve several at once, each with a distinct kid. Because
you pass the whole set to decode(), it selects the one the token was signed with.
When the provider rotates, you re-fetch the set — no code change.
Cache the key set
Don’t fetch the JWKS on every request — it changes rarely. Cache the parsed keys and
refresh periodically (respect the endpoint’s Cache-Control where you can):
function getProviderKeys(CacheInterface $cache): array
{
return $cache->remember('google_jwks', 3600, function () {
$jwks = json_decode(
file_get_contents('https://www.googleapis.com/oauth2/v3/certs'),
true
);
return JWK::parseKeySet($jwks); // array of PublicKey, keyed by kid
});
}Malformed keys are skipped, not fatal
parseKeySet() skips any individual key it can’t parse (and any entry missing a kid)
and returns the rest. A single bad entry in the provider’s document won’t break
verification for the good keys.
Parse a single key
If you already know which key you need, parseKey() converts one JWK object into a
PublicKey:
use Flytachi\Jwt\JWK;
$publicKey = JWK::parseKey([
'kty' => 'RSA',
'alg' => 'RS256',
'n' => '0vx7agoebGcQ...',
'e' => 'AQAB',
]);Supported key types
The parser handles the three standard JWK key types:
kty |
Required fields | Produces |
|---|---|---|
RSA |
n, e |
RSA public key for RS* verification |
EC |
crv, x, y |
ECDSA public key (P-256/P-384/P-521) for ES* |
oct |
k |
Symmetric secret for HS* verification |
Every JWK must also carry alg (the algorithm) — the parser throws without it, since
the resulting PublicKey is pinned to that algorithm.
Always validate the claims too
A valid signature only proves who signed it. For third-party tokens you must also
check the token was meant for you and issued by whom you expect — verify iss and
aud yourself after decoding:
use Flytachi\Jwt\JWTException;
$payload = JWT::decode($idToken, $publicKeys); // signature + exp/nbf/iat
if ($payload->getClaim('iss') !== 'https://accounts.google.com') {
throw new JWTException('Unexpected issuer.');
}
if ($payload->getClaim('aud') !== 'my-client-id.apps.googleusercontent.com') {
throw new JWTException('Token was not issued for this app.');
}
$userId = $payload->getClaim('sub');exp is checked, iss/aud are not
decode() validates the time claims (exp, nbf, iat) automatically, but it does
not check iss or aud — those are application-specific. Always assert them
yourself when consuming tokens you didn’t issue.
Related
- Working with claims — reading and validating claims
- Asymmetric keys — issuing your own
kid-tagged tokens - API reference —
JWK::parseKeySet()/parseKey()