Проверка iss и aud как обязательный шаг

Поля iss (issuer) и aud (audience) являются ключевыми элементами полезной нагрузки JWT и выполняют роль контекстной привязки токена:

  • iss определяет сторону, выпустившую токен (сервер авторизации или доверенный провайдер)
  • aud указывает сторону, для которой токен предназначен (API, сервис, клиентское приложение)

Игнорирование этих полей делает проверку подписи недостаточной: даже корректно подписанный токен может оказаться выпущенным посторонней системой или предназначенным для другого сервиса.


Роль библиотеки jose в валидации

Библиотека jose предоставляет встроенные механизмы строгой проверки iss и aud при декодировании и валидации JWT. Это позволяет избежать ручных проверок и снижает риск ошибок.

Ключевой метод: jwtVerify

import { jwtVerify } from 'jose'

Базовая проверка с обязательными iss и aud

const { 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'

Особенности:

  • Сравнение выполняется как строка
  • Не допускается частичное совпадение
  • Не применяется нормализация URL

Типичная ошибка:

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"
}

Это означает:

  • токен не содержит iss
  • параметр issuer был указан в проверке

Поддержка нескольких допустимых значений

Иногда система доверяет нескольким источникам или обслуживает несколько клиентов.

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 Provider
  • aud гарантирует, что токен предназначен именно текущему сервису

Пример архитектуры:

  • 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
  • Использовать точные значения (без сокращений)
  • Не полагаться на ручные проверки
  • Исключать “гибкие” сравнения строк
  • Проверять оба поля одновременно

Проверка вместе с другими claim

Обычно iss и aud проверяются в связке с:

  • exp — срок действия
  • nbf — время начала действия
  • sub — идентификатор субъекта
await jwtVerify(token, publicKey, {
  issuer: 'https://auth.example.com',
  audience: 'api://my-service',
  subject: 'user-123'
})

Итоговая схема валидации JWT

  1. Проверка подписи
  2. Проверка времени (exp, nbf)
  3. Проверка iss
  4. Проверка aud
  5. Проверка дополнительных claim

Нарушение любого этапа приводит к отклонению токена.


Влияние на архитектуру безопасности

Обязательная проверка iss и aud:

  • устраняет возможность повторного использования токенов
  • предотвращает атаки с подменой issuer
  • обеспечивает строгую изоляцию сервисов
  • делает систему устойчивой к ошибкам конфигурации

При использовании jose эти проверки становятся декларативными и встроенными, что минимизирует риск неправильной реализации.