Aller au contenu
ecdsa.com
.NET

Microsoft.IdentityModel (JsonWebTokenHandler)

Le JsonWebTokenHandler de Microsoft.IdentityModel, le validateur JWT sur lequel reposent l’authentification ASP.NET Core et les SDK Microsoft Entra ID : cinq minutes de tolérance de décalage d’horloge par défaut, exp obligatoire, et des jetons ES256 acceptés même signés avec une clé P-384.
Licence
MIT
Version vérifiée
8.23.0 ·
Code source
github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet
Documentation
learn.microsoft.com/dotnet/api/microsoft.identitymodel.jsonwebtokens.jsonwebtokenhandler

Compatibilité en un coup d’œil

JOSE et JWT

Algorithmes JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512

Microsoft.IdentityModel.JsonWebTokens crée et valide des JWT avec JsonWebTokenHandler et un objet TokenValidationParameters ; dans ASP.NET Core, AddJwtBearer y fait passer les requêtes, si bien que ses valeurs par défaut sont celles de la plupart des API .NET. La version 8.23.0 a signé et validé les familles HS, RS, PS et ES ; le paquet ne contient aucun type de clé de sécurité EdDSA.

ValidAlgorithms vaut null par défaut, et c’est la clé qui décide : un jeton RS256 et un jeton HS256 forgé avec le texte PEM de la clé EC ont tous deux échoué face à une clé EC avec IDX10517: Signature validation failed. Les jetons non signés échouent avec IDX10504, sauf si RequireSignedTokens est défini à false. La lacune porte sur la liaison à la courbe : ES256 a signé avec une clé P-384, et le même gestionnaire a validé le jeton obtenu, ce que d’autres bibliothèques (jose, PyJWT, golang-jwt) refusent. Définissez ValidAlgorithms et utilisez des clés de la bonne taille, comme le fait l’exemple.

Les règles de durée de vie sont l’inverse de celles de la plupart des bibliothèques : exp est obligatoire (IDX10225) et TokenValidationParameters.DefaultClockSkew vaut cinq minutes, si bien qu’un jeton reste accepté jusqu’à cinq minutes après son expiration. C’est généreux pour des jetons d’accès à courte durée de vie : définissez ClockSkew explicitement. CreateToken ajoute exp même quand Expires n’est pas défini.

Les signatures ES256 sont sous la forme r‖s brute de 64 octets qu’exige JWS, ce qui correspond au format par défaut de la classe ECDsa de .NET ; une signature DER échoue avec le même IDX10517 qu’une contrefaçon. Les messages d’erreur masquent le contenu des jetons sous la forme [PII of type ... is hidden], sauf si IdentityModelEventSource.ShowPII est activé pendant le débogage : décodez plutôt le jeton dans le débogueur JWT, et contrôlez les clés d’un fournisseur avec le vérificateur JWKS.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/microsoft-identitymodel/run.sh a exécuté Microsoft.IdentityModel (JsonWebTokenHandler) 8.23.0 dans l’image de conteneur mcr.microsoft.com/dotnet/sdk:10.0.402-noble le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Algorithmes JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512

    Signature et validation réussies avec HS, RS, PS et ES en 256/384/512 ; le paquet ne contient aucun type de clé EdDSA.

    Relevé par la vérification
    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
  • Encodage des signatures ES256

    raw

    Les signatures ES256 font 64 octets, en r‖s brut.

    Relevé par la vérification
    fact jws-ecdsa-encoding = raw
  • Signature DER dans un JWS

    rejected

    Une signature ES256 encodée en DER échoue avec IDX10517: Signature validation failed.

    Relevé par la vérification
    fact jws-der-signature = rejected
    evidence jws-der-signature: SecurityTokenSignatureKeyNotFoundException: IDX10517: Signature validation failed. The token's kid is missing. Keys tried:
  • Liste d’algorithmes autorisés

    from-key

    Avec ValidAlgorithms à null, c’est la clé qui décide : un jeton RS256 présenté à une clé EC a échoué avec IDX10517.

    Relevé par la vérification
    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:
  • Jetons non signés (alg none)

    opt-in

    Par défaut, les jetons non signés échouent avec IDX10504: Unable to validate signature, token does not have a signature, et passent la validation avec RequireSignedTokens = false.

    Relevé par la vérification
    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
  • Clé publique utilisée comme secret HMAC

    rejected

    Un jeton HS256 dont la clé est le texte PEM a échoué face à la clé EC avec IDX10517.

    Relevé par la vérification
    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:
  • Courbe vérifiée par rapport à alg

    not-enforced

    CreateToken a signé en ES256 avec une clé P-384, et la validation avec cette clé et ValidAlgorithms limité à ES256 a réussi.

    Relevé par la vérification
    fact jwt-ecdsa-curve-check = not-enforced
    evidence jwt-ecdsa-curve-check: ES256 with a P-384 key: signed; validation: valid
  • Décalage d’horloge toléré par défaut

    300s

    TokenValidationParameters.DefaultClockSkew vaut 5 minutes : des jetons expirés jusqu’à environ 300 secondes plus tôt ont encore passé la validation.

    Relevé par la vérification
    fact jwt-clock-skew = 300s
    evidence jwt-clock-skew: TokenValidationParameters.DefaultClockSkew = 00:05:00
  • Claim d’expiration obligatoire

    yes

    Un jeton sans exp échoue avec SecurityTokenNoExpirationException: IDX10225.

    Relevé par la vérification
    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:
  • Détails des erreurs masqués par défaut

    yes

    Les erreurs de validation remplacent le contenu des jetons par [PII of type ... is hidden] ; définir IdentityModelEventSource.ShowPII = true a supprimé ce texte de substitution.

    Relevé par la vérification
    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

Utilisation

Créer un jeton ES256 et le valider avec l’algorithme, l’émetteur et l’audience fixés, et une tolérance de décalage d’horloge réduite à 30 secondes.
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"]}");

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur mcr.microsoft.com/dotnet/sdk:10.0.402-noble, et l’exécution échoue si la sortie ne correspond pas.

Mise en place
dotnet new console --force -o . >/dev/null && dotnet add package Microsoft.IdentityModel.JsonWebTokens --version 8.23.0 >/dev/null
Commande
rm -f Program.cs && DOTNET_NOLOGO=1 dotnet run 2>/dev/null
La sortie doit correspondre à cette expression régulière
valid: True, sub: user-42

Bibliothèques