Skip to content

Creating JWT Tokens with Microsoft.IdentityModel.JsonWebTokens ​

Problem Statement ​

When migrating from System.IdentityModel.Tokens.Jwt to the newer Microsoft.IdentityModel.JsonWebTokens library, developers often struggle to convert existing token generation code. The original approach (JwtSecurityTokenHandler) uses a different pattern than the newer JsonWebTokenHandler, leading to confusion about:

  • Proper token descriptor configuration
  • Claims representation
  • Timestamp handling
  • Signing credential setup

Solution: Using JsonWebTokenHandler ​

Step-by-Step Implementation ​

Here's the modern approach using Microsoft.IdentityModel.JsonWebTokens:

cs
using System.Text;
using Microsoft.IdentityModel.Tokens;

// 1. Prepare security key
byte[] keyData = Encoding.UTF8.GetBytes("SomeStringFromConfig1234");
var securityKey = new SymmetricSecurityKey(keyData);

// 2. Configure claims (as dictionary)
var claims = new Dictionary<string, object>
{
    [ClaimTypes.Name] = "Testuser",
    [ClaimTypes.GroupSid] = "Tenant1",
    [ClaimTypes.Sid] = "3c545f1c-cc1b-4cd5-985b-8666886f985b"
};

// 3. Set up token descriptor
var descriptor = new SecurityTokenDescriptor
{
    Issuer = "MyIssuer",
    Audience = "MyAudience",
    Claims = claims,
    IssuedAt = null,          // Explicitly disable auto-generation
    NotBefore = DateTime.UtcNow,
    Expires = DateTime.UtcNow.AddMinutes(120),
    SigningCredentials = new SigningCredentials(
        securityKey, 
        SecurityAlgorithms.HmacSha256Signature
    )
};

// 4. Create handler and generate token
var handler = new JsonWebTokenHandler();
handler.SetDefaultTimesOnTokenCreation = false; // Critical for manual timestamp control

string tokenString = handler.CreateToken(descriptor);

Key Differences Explained ​

  1. Claims Representation
    Claims are now a Dictionary<string, object> instead of List<Claim>. This directly maps to JWT claims without conversion overhead.

  2. Timestamp Control
    By setting SetDefaultTimesOnTokenCreation = false, we:

    • Disable automatic iat (IssuedAt) generation
    • Gain explicit control over nbf (NotBefore) and exp (Expiry)
    • Avoid duplicate timestamp claims
  3. Token Creation Workflow
    Uses a handler/descriptor pattern instead of constructing token objects:

    JsonWebTokenHandler + SecurityTokenDescriptor → CreateToken()

Output Validation ​

Both approaches generate identical JWT payloads:

json
{
  "aud": "MyAudience",
  "iss": "MyIssuer",
  "exp": 1709078400,
  "nbf": 1708992000,
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Testuser",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid": "Tenant1",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid": "3c545f1c-cc1b-4cd5-985b-8666886f985b"
}

Important Considerations ​

  • Key Length: HMAC-SHA256 requires keys ≥256 bits (32+ characters)
  • Clock Skew: Production systems should handle minor time mismatches
  • Token Validation: Pair with TokenValidationParameters for validation

Performance Benefit

Microsoft benchmarks show ~30% faster token processing with JsonWebTokenHandler versus the legacy library

Common Errors ​

  • Automatic timestamps: Forgetting handler.SetDefaultTimesOnTokenCreation = false adds iat
  • Byte padding: Incorrect key encoding causes security exceptions
  • Claim collisions: Duplicate keys in dictionary result in overwrites

Best Practices ​

  1. Store sensitive keys in secure configuration (Azure Key Vault, environment variables)
  2. Validate tokens using the same library version
  3. Prefer RsaSecurityKey over symmetric keys where possible
cs
var validationParams = new TokenValidationParameters
{
    ValidIssuer = "MyIssuer",
    ValidAudience = "MyAudience",
    IssuerSigningKey = securityKey
};

TokenValidationResult result = handler.ValidateToken(tokenString, validationParams);