Проверка через JWKS
Провайдеры идентификации публикуют свои публичные ключи в виде JSON Web Key Set (JWKS)
по известному URL. Парсер JWK превращает этот документ в карту PublicKey,
проиндексированную по kid, так что вы можете проверять токены провайдера — и переживать
ротацию ключей — без какого-либо жёсткого кодирования.
Загрузка и разбор набора ключей
JWK::parseKeySet() принимает декодированный массив JWKS и возвращает [kid => PublicKey] —
ровно ту форму, которая нужна decode():
<?php
use Flytachi\Jwt\JWK;
use Flytachi\Jwt\JWT;
// 1. Загружаем JWKS-документ провайдера
$jwksJson = file_get_contents('https://www.googleapis.com/oauth2/v3/certs');
$jwks = json_decode($jwksJson, true);
// 2. Разбираем его в [kid => PublicKey]
$publicKeys = JWK::parseKeySet($jwks);
// 3. Проверяем ID-токен — decode() сопоставляет kid токена с ключом
$payload = JWT::decode($idToken, $publicKeys);
echo $payload->getClaim('email');Почему ротация kid просто работает
Провайдеры выполняют ротацию ключей и отдают сразу несколько ключей, каждый со своим
kid. Поскольку вы передаёте в decode() весь набор, он выбирает тот, которым был
подписан токен. Когда провайдер делает ротацию, вы просто заново загружаете набор — без
изменений кода.
Кеширование набора ключей
Не загружайте JWKS при каждом запросе — он меняется редко. Кешируйте разобранные ключи и
периодически обновляйте их (учитывайте Cache-Control конечной точки, где это возможно):
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); // массив PublicKey, проиндексированный по kid
});
}Некорректные ключи пропускаются, а не приводят к ошибке
parseKeySet() пропускает любой отдельный ключ, который не может разобрать (и любую запись
без kid), и возвращает остальные. Одна плохая запись в документе провайдера не сломает
проверку для корректных ключей.
Разбор одного ключа
Если вы уже знаете, какой ключ вам нужен, parseKey() преобразует один объект JWK в
PublicKey:
use Flytachi\Jwt\JWK;
$publicKey = JWK::parseKey([
'kty' => 'RSA',
'alg' => 'RS256',
'n' => '0vx7agoebGcQ...',
'e' => 'AQAB',
]);Поддерживаемые типы ключей
Парсер обрабатывает три стандартных типа ключей JWK:
kty |
Обязательные поля | Создаёт |
|---|---|---|
RSA |
n, e |
Публичный ключ RSA для проверки RS* |
EC |
crv, x, y |
Публичный ключ ECDSA (P-256/P-384/P-521) для ES* |
oct |
k |
Симметричный секрет для проверки HS* |
Каждый JWK также должен нести alg (алгоритм) — без него парсер выбрасывает исключение,
поскольку итоговый PublicKey привязан к этому алгоритму.
Всегда проверяйте и claims
Действительная подпись доказывает лишь кто её поставил. Для сторонних токенов вы также
должны проверить, что токен был предназначен вам и выпущен тем, кого вы ожидаете — проверяйте
iss и aud самостоятельно после декодирования:
use Flytachi\Jwt\JWTException;
$payload = JWT::decode($idToken, $publicKeys); // подпись + 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 проверяется, iss/aud — нет
decode() автоматически проверяет временные claims (exp, nbf, iat), но не
проверяет iss или aud — они специфичны для приложения. Всегда проверяйте их
самостоятельно, когда потребляете токены, которые выпустили не вы.
Связанные материалы
- «Работа с claims» — чтение и проверка claims
- «Асимметричные ключи» — выпуск собственных токенов с меткой
kid - «Справочник API» —
JWK::parseKeySet()/parseKey()