Перейти к содержимому
ecdsa.com
.NET

Microsoft.IdentityModel (JsonWebTokenHandler)

JsonWebTokenHandler из Microsoft.IdentityModel — валидатор JWT, на котором основаны аутентификация в ASP.NET Core и SDK для Microsoft Entra ID: допуск на расхождение часов по умолчанию 5 минут, exp обязателен, а токены ES256 принимаются и с ключами P-384.
Лицензия
MIT
Проверенная версия
8.23.0 ·
Исходный код
github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet
Документация
learn.microsoft.com/dotnet/api/microsoft.identitymodel.jsonwebtokens.jsonwebtokenhandler

Совместимость вкратце

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512

Microsoft.IdentityModel.JsonWebTokens создаёт и проверяет JWT с помощью JsonWebTokenHandler и объекта TokenValidationParameters; AddJwtBearer в ASP.NET Core пропускает через него запросы, поэтому его настройки по умолчанию становятся настройками по умолчанию большинства API на .NET. В версии 8.23.0 подпись и проверка работали для семейств HS, RS, PS и ES; типа ключа безопасности для EdDSA в пакете нет.

По умолчанию ValidAlgorithms равно null, и всё решает ключ: токен RS256 и токен HS256, подделанный с текстом PEM ключа EC в качестве секрета, при проверке ключом EC завершились ошибкой IDX10517: Signature validation failed. Неподписанные токены отклоняются с ошибкой IDX10504, если не установить RequireSignedTokens в false. Слабое место — привязка к кривой: токен ES256 удалось подписать ключом P-384, и тот же обработчик принял его, тогда как другие библиотеки (jose, PyJWT, golang-jwt) такой токен отклоняют. Задайте ValidAlgorithms и используйте ключи нужного размера, как это делает пример.

Правила срока действия противоположны принятым в большинстве библиотек: exp обязателен (IDX10225), а TokenValidationParameters.DefaultClockSkew равен 5 минутам, поэтому токен продолжает работать до 5 минут после истечения срока действия. Для короткоживущих токенов доступа это большой запас; задайте ClockSkew явно. CreateToken добавляет exp, даже если Expires не задан.

Подписи ES256 — это 64-байтовый формат raw r‖s, которого требует JWS и который совпадает с форматом по умолчанию собственного класса ECDsa в .NET; подпись DER не проходит проверку с той же ошибкой IDX10517, что и подделка. Сообщения об ошибках скрывают содержимое токена за [PII of type ... is hidden], если при отладке не включить IdentityModelEventSource.ShowPII; вместо этого декодируйте токен в отладчике JWT, а ключи поставщика проверьте с помощью проверки JWKS.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/microsoft-identitymodel/run.sh запустила Microsoft.IdentityModel (JsonWebTokenHandler) 8.23.0 в образе контейнера mcr.microsoft.com/dotnet/sdk:10.0.402-noble; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512

    Подпись и проверка работают для HS, RS, PS и ES на 256/384/512; типа ключа для EdDSA в пакете нет.

    Что записала проверка
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512
    evidence jws-algorithms: EdDSA signing with an ECDsaSecurityKey or other built-in key: no EdDSA security key type
  • Кодировка подписи ES256

    raw

    Подписи ES256 — 64 байта в формате raw r‖s.

    Что записала проверка
    fact jws-ecdsa-encoding = raw
  • Подпись в DER внутри JWS

    rejected

    Подпись ES256 в кодировке DER не проходит проверку с ошибкой IDX10517: Signature validation failed.

    Что записала проверка
    fact jws-der-signature = rejected
    evidence jws-der-signature: SecurityTokenSignatureKeyNotFoundException: IDX10517: Signature validation failed. The token's kid is missing. Keys tried:
  • Список разрешённых алгоритмов

    from-key

    Если ValidAlgorithms равно null, всё решает ключ: проверка токена RS256 ключом EC завершилась ошибкой IDX10517.

    Что записала проверка
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: ValidAlgorithms null, RS256 token against the EC key: SecurityTokenSignatureKeyNotFoundException: IDX10517: Signature validation failed. The token's kid is missing. Keys tried:
  • Неподписанные токены (alg none)

    opt-in

    По умолчанию неподписанные токены отклоняются с ошибкой IDX10504: Unable to validate signature, token does not have a signature, а с RequireSignedTokens = false проходят проверку.

    Что записала проверка
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: default parameters: SecurityTokenInvalidSignatureException: IDX10504: Unable to validate signature, token does not have a signature:
    evidence jwt-alg-none: RequireSignedTokens = false: valid
  • Открытый ключ в роли секрета HMAC

    rejected

    Токен HS256, подписанный текстом PEM в качестве ключа, не прошёл проверку ключом EC: ошибка IDX10517.

    Что записала проверка
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: HS256 token keyed with the PEM text, EC key, ValidAlgorithms null: SecurityTokenSignatureKeyNotFoundException: IDX10517: Signature validation failed. The token's kid is missing. Keys tried:
  • Сверка кривой с alg

    not-enforced

    CreateToken подписал токен ES256 ключом P-384, и проверка с этим ключом и ValidAlgorithms, равным ES256, прошла успешно.

    Что записала проверка
    fact jwt-ecdsa-curve-check = not-enforced
    evidence jwt-ecdsa-curve-check: ES256 with a P-384 key: signed; validation: valid
  • Допустимое расхождение часов по умолчанию

    300s

    TokenValidationParameters.DefaultClockSkew равен 5 минутам: токены, срок действия которых истёк не более чем примерно 300 секунд назад, всё ещё проходили проверку.

    Что записала проверка
    fact jwt-clock-skew = 300s
    evidence jwt-clock-skew: TokenValidationParameters.DefaultClockSkew = 00:05:00
  • Обязательное утверждение о сроке действия

    yes

    Токен без exp отклоняется с ошибкой SecurityTokenNoExpirationException: IDX10225.

    Что записала проверка
    fact jwt-exp-required = yes
    evidence jwt-exp-required: CreateToken without Expires adds exp anyway: True
    evidence jwt-exp-required: hand-built HS256 token without exp: SecurityTokenNoExpirationException: IDX10225: Lifetime validation failed. The token is missing an Expiration Time. Tokentype:
  • Подробности ошибок скрыты по умолчанию

    yes

    В ошибках проверки содержимое токена заменяется на [PII of type ... is hidden]; после установки IdentityModelEventSource.ShowPII = true эта заглушка исчезла.

    Что записала проверка
    fact error-detail-hidden-by-default = yes
    evidence error-detail-hidden-by-default: PII placeholder in the default message: True; after IdentityModelEventSource.ShowPII = true: False

Использование

Создайте токен ES256 и проверьте его с фиксированными алгоритмом, издателем и аудиторией и с допуском на расхождение часов, уменьшенным до 30 секунд.
using System.Security.Cryptography;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

var key = new ECDsaSecurityKey(ECDsa.Create(ECCurve.NamedCurves.nistP256)) { KeyId = "2026-10" };
var handler = new JsonWebTokenHandler();
var token = handler.CreateToken(new SecurityTokenDescriptor
{
    Issuer = "https://auth.example.com",
    Audience = "api",
    Claims = new Dictionary<string, object> { ["sub"] = "user-42" },
    Expires = DateTime.UtcNow.AddMinutes(10),
    SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256),
});

// Pin the algorithm (ES256 is not tied to P-256 here) and shrink the 5-minute default skew.
var result = await handler.ValidateTokenAsync(token, new TokenValidationParameters
{
    IssuerSigningKey = key,
    ValidIssuer = "https://auth.example.com",
    ValidAudience = "api",
    ValidAlgorithms = new[] { SecurityAlgorithms.EcdsaSha256 },
    ClockSkew = TimeSpan.FromSeconds(30),
});
Console.WriteLine($"valid: {result.IsValid}, sub: {result.Claims["sub"]}");

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера mcr.microsoft.com/dotnet/sdk:10.0.402-noble, и запуск завершается ошибкой, если вывод не совпадает.

Подготовка
dotnet new console --force -o . >/dev/null && dotnet add package Microsoft.IdentityModel.JsonWebTokens --version 8.23.0 >/dev/null
Команда
rm -f Program.cs && DOTNET_NOLOGO=1 dotnet run 2>/dev/null
Вывод должен совпадать (регулярное выражение)
valid: True, sub: user-42

Библиотеки