Поля iss (issuer) и aud (audience) являются
ключевыми элементами полезной нагрузки JWT и выполняют роль контекстной
привязки токена:
iss определяет сторону, выпустившую
токен (сервер авторизации или доверенный провайдер)aud указывает сторону, для которой
токен предназначен (API, сервис, клиентское приложение)Игнорирование этих полей делает проверку подписи недостаточной: даже корректно подписанный токен может оказаться выпущенным посторонней системой или предназначенным для другого сервиса.
jose в валидацииБиблиотека jose предоставляет встроенные механизмы
строгой проверки iss и aud при декодировании и
валидации JWT. Это позволяет избежать ручных проверок и снижает риск
ошибок.
Ключевой метод: jwtVerify
import { jwtVerify } from 'jose'
iss и audconst { payload } = await jwtVerify(token, publicKey, {
issuer: 'https://auth.example.com',
audience: 'api://my-service'
})
Что происходит:
exp, nbf)iss с ожидаемым значениемaud содержит указанный
идентификаторПри несоответствии хотя бы одного условия выбрасывается исключение.
iss: защита от подмены источникаПоле iss должно строго соответствовать доверенному
источнику. Даже незначительные расхождения (например, отсутствие
https:// или лишний слэш) приводят к ошибке.
issuer: 'https://auth.example.com'
Особенности:
Типичная ошибка:
issuer: 'auth.example.com' // неверно, если в токене 'https://auth.example.com'
aud: контроль назначения токенаПоле aud может быть:
jose автоматически обрабатывает оба варианта.
audience: 'api://my-service'
Если в токене:
"aud": ["api://my-service", "api://another-service"]
— проверка пройдет успешно.
Важно:
aud при требовании — ошибкаЕсли issuer или audience указаны в
параметрах jwtVerify, библиотека требует их наличия в
токене.
Пример ошибки:
{
"error": "JWTClaimValidationFailed",
"message": "missing required \"iss\" claim"
}
Это означает:
ississuer был указан в проверкеИногда система доверяет нескольким источникам или обслуживает несколько клиентов.
await jwtVerify(token, publicKey, {
issuer: ['https://auth1.example.com', 'https://auth2.example.com'],
audience: ['api://service-a', 'api://service-b']
})
Проверка пройдет, если:
iss совпадает с любым из спискаaud содержит хотя бы одно совпадениеiss + audИспользование только одного поля недостаточно:
| Проверка | Уязвимость |
|---|---|
Только iss |
токен может быть предназначен другому сервису |
Только aud |
токен может быть выпущен злоумышленником |
iss + aud |
строгая привязка источника и назначения |
jose выбрасывает типизированные ошибки:
import { errors } from 'jose'
Пример:
try {
await jwtVerify(token, publicKey, {
issuer: 'https://auth.example.com',
audience: 'api://my-service'
})
} catch (err) {
if (err instanceof errors.JWTClaimValidationFailed) {
console.error('Ошибка claim:', err.message)
}
}
В распределённых системах проверка iss и
aud становится критически важной:
iss гарантирует, что токен выпущен
доверенным Identity Provideraud гарантирует, что токен
предназначен именно текущему сервисуПример архитектуры:
auth.example.com — выпускает токеныapi://orders — сервис заказовapi://payments — сервис платежейТокен для orders не должен приниматься
payments.
1. Отсутствие проверки aud
await jwtVerify(token, key) // небезопасно
2. Использование неполного iss
issuer: 'example.com' // вместо полного URL
3. Игнорирование массива aud
Ручные проверки часто не учитывают массивы, в отличие от
jose.
4. Сравнение с использованием
includes
payload.iss.includes('example.com') // уязвимость
issuer и audience в
jwtVerifyОбычно iss и aud проверяются в связке
с:
exp — срок действияnbf — время начала действияsub — идентификатор субъектаawait jwtVerify(token, publicKey, {
issuer: 'https://auth.example.com',
audience: 'api://my-service',
subject: 'user-123'
})
exp, nbf)issaudНарушение любого этапа приводит к отклонению токена.
Обязательная проверка iss и aud:
При использовании jose эти проверки становятся
декларативными и встроенными, что минимизирует риск неправильной
реализации.