#:package Microsoft.IdentityModel.JsonWebTokens@8.23.0using System.Security.Cryptography;using Microsoft.IdentityModel.JsonWebTokens;using Microsoft.IdentityModel.Tokens; using var ecdsa = ECDsa.Create();ecdsa.ImportFromPem(File.ReadAllText("key.pem")); // PKCS#8 or "EC PRIVATE KEY"var signingKey = new ECDsaSecurityKey(ecdsa) { KeyId = "2026-10" }; // becomes the kid header var now = DateTime.UtcNow;string token = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor{ Issuer = "https://issuer.example.com", Audience = "api://orders", Claims = new Dictionary<string, object> { ["sub"] = "user-42" }, IssuedAt = now, NotBefore = now, Expires = now.AddMinutes(15), SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.EcdsaSha256), // "ES256"});Console.WriteLine(token);How it works
ECDsa.ImportFromPemreads PKCS#8 orEC PRIVATE KEYPEM;ECDsaSecurityKeywraps it, and itsKeyIdbecomes thekidheader.SecurityAlgorithms.EcdsaSha256is the string"ES256".Issuer,Audience,IssuedAt,NotBeforeandExpiresmap toiss,aud,iat,nbfandexp; other claims go inClaims.JsonWebTokenHandleris the current API;JwtSecurityTokenHandlerin System.IdentityModel.Tokens.Jwt is its predecessor, still found in older ASP.NET examples.
Gotchas
- The handler does not check that the key fits the algorithm: a P-384 key with
EcdsaSha256produces a token with a 96-byte signature that every ES256 verifier rejects. Use P-256 keys for ES256. - Leaving out
Expiresdoes not create a token without expiry: by default the handler addsiat,nbfand anexp60 minutes later. Set the lifetime explicitly so it is visible in code. DateTime.Nowis local time; useDateTime.UtcNow(orDateTimeOffset.UtcNow) for token times.- The credentials keep a reference to the
ECDsa, not a copy: once it is disposed, the nextCreateTokenthrowsObjectDisposedException: Cannot access a disposed object.Keep the key alive as long as the credentials, for example as a singleton.