open atlas
↑ К треку
Безопасность SEC · 02 · 03

Валидация ID-токена и управление JWKS-кешем

Восемь обязательных проверок каждого OIDC клиента, почему устаревший JWKS-кеш вызывает auth-аутаж, и паттерн on-cache-miss refresh.

SEC Middle ◷ 9 min
Уровень
ОсновыJuniorMiddleSenior

IdP ротирует signing key. JWKS-кеш (JSON Web Key Set — набор публичных ключей IdP) твоего приложения имеет TTL 6 часов. Следующие 4 часа каждый новый пользователь получает 401 — его id_token подписан новым ключом, которого нет в кеше. Валидация подписи fail-ит на токене, который полностью валиден. К концу урока ты будешь знать все восемь проверок, стоящих между тобой и CVE, и как сделать ротацию ключей невидимой для пользователей.

Восемь обязательных проверок id_token

Когда твоё приложение получает id_token от OIDC authorization server, оно обязано верифицировать все восемь следующих пунктов перед тем, как доверять identity пользователя. Пропуск любого — документированная CVE категория.

1. Распарсить JWT. Split header.payload.signature. Base64url-декодировать каждую часть.

2. Прочитать kid из JWT header. Поле kid (Key ID) называет, каким публичным ключом подписан этот токен. Без него нельзя найти правильный ключ.

3. Получить публичный ключ из JWKS. Вызвать /.well-known/jwks.json IdP (кешировано, TTL 5–15 мин). Найти ключ с совпадающим kid. При cache miss — немедленно обновить перед тем, как провалиться.

4. Верифицировать подпись. Использовать алгоритм, объявленный в header (должен быть RS256 или ES256 — никогда HS256 с асимметричным ключом, никогда alg: none). Fail подписи → отклонить. Закрепить алгоритм server-side; никогда не доверять alg claim самого токена.

5. Валидировать iss. Должен точно совпадать с ожидаемым URL issuer из конфига. Атакующий может выпустить токен от своего IdP с валидной подписью.

6. Валидировать aud. Должен содержать твой client_id. Без этой проверки токен, выпущенный для другого приложения, принимается твоим.

7. Валидировать exp и iat. exp должен быть в будущем; iat — в прошлом. Допустимо ~5 минут clock skew (300 секунд — типичная tolerance). Обе проверки используют часы сервера, не клиента.

8. Валидировать nonce. Должен совпадать с nonce, отправленным клиентом в /authorize. Без этой проверки захваченный id_token можно replay-нуть в другую сессию.

Последовательность валидации id_token
1
Парсинг JWTSplit header.payload.signature
2
kid lookupЧитаем key ID из header → находим ключ в JWKS
3
ПодписьВерифицируем публичным ключом; отклонить alg=none или неверный тип
4
issДолжен совпасть с ожидаемым issuer (exact string)
5
audДолжен содержать ВАШ client_id
6
exp / iatexp в будущем, iat в прошлом — допустимо 300с clock skew
7
nonceДолжен совпасть с nonce из /authorize

Устаревший JWKS-кеш: production failure mode

Ты знаешь что валидировать — но откуда берутся публичные ключи и что происходит, когда они меняются под тобой? Это вопрос, с которым сталкивается каждый production deployment при ротации ключей. JWKS endpoint IdP содержит его текущие signing public keys. Твоё приложение кеширует этот список (fetching на каждый запрос был бы слишком медленным). При ротации ключей IdP публикует новый ключ — но твой кеш ещё содержит старый.

Что ломается: новые токены подписаны новым ключом. Твой валидатор читает kid из header токена, ищет в кеше — не найдено. Валидация подписи fail-ит. Пользователи не могут войти, хотя их токены полностью валидны.

Fix — on-cache-miss refresh: Когда kid не найден в кеше, немедленно сделать fresh fetch JWKS с /.well-known/jwks.json и повторить lookup. RFC 7517 явно разрешает это. Провалить только если kid всё ещё отсутствует после refresh.

Долгосрочная митигация: Короткий cache TTL (5–15 минут) плюс on-miss refresh как backstop. Mature IdP (Auth0, Okta) публикуют новые ключи за 24–48 часов до активации. Если твой не делает этого — короткий TTL + on-miss refresh — единственная надёжная защита.

Викторина

Приложение верифицирует подпись id_token и iss корректно, но пропускает проверку aud. Какая атака становится возможной?

Викторина

Почему нельзя доверять claim 'alg' в JWT header?

Расставь шаги по порядку

Поставь шаги валидации id_token в правильном порядке:

  1. 1 Парсинг JWT — split header.payload.signature
  2. 2 Читаем kid (key ID) из JWT header
  3. 3 Получаем matching public key с JWKS endpoint issuer-а (кешировано, refresh при miss)
  4. 4 Верифицируем подпись публичным ключом и pinned ожидаемым алгоритмом
  5. 5 Валидируем iss claim совпадает с ожидаемым issuer
  6. 6 Валидируем aud claim содержит client_id приложения
  7. 7 Валидируем exp в будущем и iat в прошлом (допустимо ~300с clock skew)
  8. 8 Валидируем nonce claim совпадает с nonce из /authorize
Вспомните перед уходом
  1. 01
    Что такое on-cache-miss JWKS refresh паттерн и почему он требуется?
  2. 02
    Почему aud claim должен содержать ТВОЙ client_id, а не просто любой client_id?
  3. 03
    Приложение пропускает exp/iat для производительности. Каков blast radius украденного id_token?
Итог

Валидация OIDC id_token имеет восемь обязательных шагов. Верификация подписи требует правильного публичного ключа из JWKS, выбранного по kid, с server-side pinned алгоритмом — никогда не читать из токена. Checks iss и aud предотвращают cross-issuer и cross-audience атаки. Checks exp/iat применяют freshness. Check nonce предотвращает replay. Устаревание JWKS-кеша при ротации ключей — самый частый production fail: on-cache-miss refresh плюс короткий TTL (5–15 минут) — стандартная митигация. Теперь, когда увидишь волну 401 после планового обслуживания IdP, сразу проверяй kid-not-found в метриках валидации — прежде чем грешить на собственный код.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки
встречается в204

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.