awesome-everything EN
↑ Обратно к восхождению

Безопасность

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

Суть Восемь обязательных проверок каждого OIDC клиента, почему устаревший JWKS-кеш вызывает auth-аутаж, и паттерн on-cache-miss refresh.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 9 min

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

Восемь обязательных проверок 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

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 минут) — стандартная митигация.

Связанные уроки
встречается в178
Продолжить восхождение ↑Ротация refresh-токенов и scope-based least privilege
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.