Сообщение об ошибке
unexpected "aud" claim value
jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED
Что это означает
Подпись верна, но утверждение `aud` токена не содержит значения, которое вы передали в `jwtVerify` как `audience`. jose сравнивает строки точно: проверка проходит, если хотя бы одно значение в `aud` токена (строке или массиве) равно одной из разрешённых вами аудиторий. Токен подлинный, просто он выпущен для другого получателя; если его принять, токен, выпущенный для одного API, можно будет повторно предъявить другому.
Почему это происходит
Токен выпущен для другого API
частоОдин сервер авторизации часто обслуживает несколько серверов ресурсов. Клиент, который получил токен для API заказов и отправляет его в API биллинга, получает именно эту ошибку — проверка делает свою работу. Свойство ошибки `err.payload` показывает, какие утверждения на самом деле есть в токене.
Тот же API, но идентификатор записан иначе
частоАудитория сравнивается побайтово: `https://api.example.com` и `https://api.example.com/` (косая черта в конце), http и https, логическое имя вроде `api://orders` и URL самого API — всё это разные значения. Поставщики удостоверений копируют идентификатор из своей конфигурации дословно, поэтому проверяющая сторона должна использовать точно такое же написание.
ID-токен отправлен вместо токена доступа
иногдаВ OpenID Connect `aud` ID-токена — это идентификатор клиента того приложения, через которое пользователь вошёл в систему, а не ваш API. Поэтому фронтенды, которые пересылают ID-токен вместо токена доступа, не проходят проверку аудитории ни в одном API за ними.
Клиент не запросил конкретную аудиторию
иногдаНекоторые провайдеры (Auth0, пользовательские серверы авторизации Okta, Keycloak с мапперами аудитории) указывают ваш API в `aud`, только если он назван в запросе токена. Без параметра `audience` или параметра `resource` из RFC 8707 токен выпускается для аудитории по умолчанию, например для эндпоинта userinfo.
Как исправить
- 1.
Выведите рядом то, что есть в токене, и то, что вы ожидаете
Декодируйте утверждения, не доверяя им, и посимвольно сравните `aud` со своим параметром `audience`. Исправьте ту сторону, где ошибка (параметр проверяющей стороны или конфигурацию издателя), но саму проверку оставьте.
js import { decodeJwt, jwtVerify } from "jose"; console.log("token aud:", decodeJwt(token).aud); // string or array of strings await jwtVerify(token, jwks, { algorithms: ["ES256"], issuer: "https://issuer.example.com", audience: "https://api.example.com/orders", // exact string match }); - 2.
Запрашивайте токен для того API, который вызываете
Если токен выпущен для другой аудитории, исправлять нужно на стороне клиента: укажите целевой API в запросе токена, чтобы сервер авторизации записал его в `aud`.
bash # OAuth 2.0 client credentials: ask the authorization server for a token # minted for the API you are about to call (Auth0 uses "audience", # RFC 8707 resource indicators use "resource"). curl -s https://issuer.example.com/oauth/token \ -d grant_type=client_credentials \ -d client_id="$CLIENT_ID" -d client_secret="$CLIENT_SECRET" \ -d audience=https://api.example.com/orders - 3.
Явно разрешите несколько идентификаторов, если все они ваши
`audience` принимает массив. Перечислите все идентификаторы, которые законно обозначают этот API (например, на время миграции с URL на логическое имя), но не удаляйте сам параметр: без `audience` jose вообще не проверяет `aud`.
js await jwtVerify(token, jwks, { algorithms: ["ES256"], audience: ["https://api.example.com/orders", "orders-api"], // any one must match });