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);