Saltar al contenido
ecdsa.com
.NET

Microsoft.IdentityModel (JsonWebTokenHandler)

JsonWebTokenHandler de Microsoft.IdentityModel, el validador de JWT que hay debajo de la autenticación de ASP.NET Core y de los SDK de Microsoft Entra ID: 5 minutos de desfase de reloj admitido por defecto, exp obligatorio y aceptación de tokens ES256 firmados con claves P-384.
Licencia
MIT
Versión comprobada
8.23.0 ·
Código fuente
github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet
Documentación
learn.microsoft.com/dotnet/api/microsoft.identitymodel.jsonwebtokens.jsonwebtokenhandler

Compatibilidad de un vistazo

JOSE y JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512

Microsoft.IdentityModel.JsonWebTokens crea y valida JWT con JsonWebTokenHandler y un objeto TokenValidationParameters; AddJwtBearer de ASP.NET Core hace pasar las solicitudes por él, y por eso sus valores predeterminados son los de la mayoría de las API de .NET. La 8.23.0 firmó y validó las familias HS, RS, PS y ES; el paquete no tiene ningún tipo de clave de seguridad para EdDSA.

ValidAlgorithms es null por defecto, y decide la clave: un token RS256 y un token HS256 falsificado con el texto PEM de la clave EC fallaron ambos contra una clave EC con IDX10517: Signature validation failed. Los tokens sin firmar fallan con IDX10504 salvo que RequireSignedTokens se establezca en false. La carencia está en la vinculación con la curva: se pudo firmar ES256 con una clave P-384 y el mismo controlador validó el token resultante, algo que otras bibliotecas —jose, PyJWT, golang-jwt— rechazan. Configura ValidAlgorithms y usa claves del tamaño adecuado, como hace el ejemplo.

Las reglas de vigencia son las contrarias a las de la mayoría de las bibliotecas: exp es obligatorio (IDX10225) y TokenValidationParameters.DefaultClockSkew es de 5 minutos, así que un token sigue funcionando hasta 5 minutos después de caducar. Es un margen generoso para tokens de acceso de corta duración; configura ClockSkew explícitamente. CreateToken añade exp incluso cuando no se establece Expires.

Las firmas ES256 son el r‖s en bruto de 64 bytes que exige JWS, igual que el valor predeterminado del propio ECDsa de .NET; una firma DER falla con el mismo IDX10517 que una falsificación. Los mensajes de error ocultan el contenido del token como [PII of type ... is hidden] salvo que se active IdentityModelEventSource.ShowPII durante la depuración; en su lugar, decodifica el token en el decodificador de JWT y comprueba las claves de un proveedor con el comprobador de JWKS.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/microsoft-identitymodel/run.sh ejecutó Microsoft.IdentityModel (JsonWebTokenHandler) 8.23.0 en la imagen de contenedor mcr.microsoft.com/dotnet/sdk:10.0.402-noble el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512

    Con HS, RS, PS y ES de 256/384/512 se firmó y se validó; el paquete no tiene ningún tipo de clave EdDSA.

    Registrado por la comprobación
    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
  • Codificación de la firma ES256

    raw

    Las firmas ES256 son 64 bytes de r‖s en bruto.

    Registrado por la comprobación
    fact jws-ecdsa-encoding = raw
  • Firma DER dentro de un JWS

    rejected

    Una firma ES256 codificada en DER falla con IDX10517: Signature validation failed.

    Registrado por la comprobación
    fact jws-der-signature = rejected
    evidence jws-der-signature: SecurityTokenSignatureKeyNotFoundException: IDX10517: Signature validation failed. The token's kid is missing. Keys tried:
  • Lista de algoritmos permitidos

    from-key

    Con ValidAlgorithms en null, decide la clave: un token RS256 contra una clave EC falló con IDX10517.

    Registrado por la comprobación
    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:
  • Tokens sin firma (alg none)

    opt-in

    Los tokens sin firmar fallan por defecto con IDX10504: Unable to validate signature, token does not have a signature y se validan con RequireSignedTokens = false.

    Registrado por la comprobación
    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
  • Clave pública usada como secreto HMAC

    rejected

    Un token HS256 con el texto PEM como clave falló contra la clave EC con IDX10517.

    Registrado por la comprobación
    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:
  • Curva cotejada con alg

    not-enforced

    CreateToken firmó ES256 con una clave P-384, y la validación con esa clave y con ValidAlgorithms establecido en ES256 tuvo éxito.

    Registrado por la comprobación
    fact jwt-ecdsa-curve-check = not-enforced
    evidence jwt-ecdsa-curve-check: ES256 with a P-384 key: signed; validation: valid
  • Desfase de reloj predeterminado

    300s

    TokenValidationParameters.DefaultClockSkew es de 5 minutos: los tokens caducados hasta unos 300 segundos antes se siguieron validando.

    Registrado por la comprobación
    fact jwt-clock-skew = 300s
    evidence jwt-clock-skew: TokenValidationParameters.DefaultClockSkew = 00:05:00
  • Claim de caducidad obligatorio

    yes

    Un token sin exp falla con SecurityTokenNoExpirationException: IDX10225.

    Registrado por la comprobación
    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:
  • Detalles de error ocultos por defecto

    yes

    Los errores de validación sustituyen el contenido del token por [PII of type ... is hidden]; establecer IdentityModelEventSource.ShowPII = true eliminó ese marcador.

    Registrado por la comprobación
    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

Uso

Crea un token ES256 y valídalo con el algoritmo, el emisor y la audiencia fijados y el desfase de reloj reducido a 30 segundos.
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"]}");

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor mcr.microsoft.com/dotnet/sdk:10.0.402-noble, y la ejecución falla si la salida no coincide.

Configuración
dotnet new console --force -o . >/dev/null && dotnet add package Microsoft.IdentityModel.JsonWebTokens --version 8.23.0 >/dev/null
Comando
rm -f Program.cs && DOTNET_NOLOGO=1 dotnet run 2>/dev/null
La salida debe coincidir (expresión regular)
valid: True, sub: user-42

Bibliotecas