Documentation

adrs/010-jwt-bearer-authentication.md

ADR-010: JWT Bearer Authentication Standard

Status

Accepted

Context

The Dynaplex architecture initially supported dual authentication mechanisms: standard JWT Bearer tokens and a custom "acsistoken" header authentication. This dual approach added complexity to the authentication pipeline, increased maintenance burden, and created potential security inconsistencies.

Historical context:

  • Legacy system used custom token headers for backwards compatibility
  • Some older clients relied on the custom authentication mechanism
  • JWT Bearer is the industry standard for API authentication
  • Supporting multiple authentication methods complicates security audits

Decision

We will standardize on JWT Bearer authentication only for all Dynaplex services. The custom "acsistoken" header authentication will be removed completely.

Implementation approach:

  • Use standard Authorization: Bearer <token> headers
  • Remove AcsisTokenAuthenticationHandler and related code
  • Configure JWT Bearer authentication in all services
  • Use Microsoft Identity platform for token validation
  • Implement consistent token validation across all services

JWT Configuration

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = configuration["Identity:Authority"];
        options.Audience = configuration["Identity:Audience"];
        options.RequireHttpsMetadata = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.FromMinutes(5)
        };
    });

Consequences

Positive

  • Standards Compliance: Following OAuth 2.0 and OpenID Connect standards
  • Security: Single, well-tested authentication mechanism
  • Tooling Support: Better support in API clients, Postman, etc.
  • Simplicity: Reduced code complexity and maintenance
  • Interoperability: Easier integration with third-party services
  • Documentation: Standard authentication is well documented

Negative

  • Breaking Change: Clients using custom token must be updated
  • Migration Effort: Need to update all existing client applications
  • No Fallback: Cannot support legacy clients during transition

Neutral

  • Token Format: JWT tokens are larger than custom tokens
  • Performance: JWT validation has cryptographic overhead
  • Debugging: Standard tools can inspect JWT tokens

Implementation Notes

Service Configuration

Each service must configure JWT Bearer:

// In Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer();

builder.Services.AddAuthorization();

// After building
app.UseAuthentication();
app.UseAuthorization();

Token Claims

Standard claims to include:

  • sub: User identifier
  • name: User display name
  • email: User email address
  • roles: User roles array
  • permissions: Optional fine-grained permissions
  • iat: Issued at timestamp
  • exp: Expiration timestamp
  • jti: Unique token identifier

Migration Strategy

  1. Phase 1: Add JWT support alongside custom tokens
  2. Phase 2: Update all clients to use JWT
  3. Phase 3: Deprecate custom token endpoints
  4. Phase 4: Remove custom token authentication code

Client Updates

Update all API clients to use Bearer tokens:

// Old way (removed)
client.DefaultRequestHeaders.Add("acsistoken", token);

// New way
client.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue("Bearer", token);

Token Generation

Identity service generates tokens:

public string GenerateJwtToken(User user)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"]);
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.Username),
            new Claim(ClaimTypes.Email, user.Email)
        }),
        Expires = DateTime.UtcNow.AddHours(8),
        SigningCredentials = new SigningCredentials(
            new SymmetricSecurityKey(key),
            SecurityAlgorithms.HmacSha256Signature)
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}

Security Best Practices

  1. Use HTTPS only for token transmission
  2. Implement token refresh mechanism
  3. Short token lifetime (15-60 minutes)
  4. Secure token storage on client side
  5. Implement token revocation list if needed
  6. Log authentication events for auditing
  • ADR-007: Migration to .NET Aspire Microservices (requires consistent auth)
  • ADR-018: Removing MediatR and CQRS Patterns (simplification theme)
  • ADR-021: Microsoft Kiota for API Client Generation (generates Bearer auth code)