Jwt Misconfiguration in Aspnet with Mutual Tls
Jwt Misconfiguration in Aspnet with Mutual Tls — how this specific combination creates or exposes the vulnerability
In ASP.NET applications, JWT misconfiguration combined with Mutual TLS (mTLS) can undermine the protections that mTLS provides and introduce authentication bypass or token confusion risks. mTLS ensures the client presents a valid certificate during the TLS handshake, adding a strong layer of transport‑layer identity. However, if JWT validation is misconfigured, the server may accept tokens that are missing, malformed, or issued to a different audience or issuer, allowing an authenticated mTLS client to be treated as an unauthenticated request or to be impersonated.
A common pattern is to rely on mTLS for client identity while still expecting a JWT for authorization or claims. If the JWT middleware is not explicitly configured to validate issuer, audience, signing keys, or token lifetime, an attacker who obtains a valid client certificate (for example, via theft or misconfigured certificate provisioning) can present that certificate and send a JWT crafted or supplied by an attacker. Because mTLS only verifies the client’s possession of the certificate, the server may trust the JWT contents without proper validation, leading to privilege escalation or unauthorized access.
Another specific risk arises when the ASP.NET pipeline order does not enforce token validation before relying on mTLS identity. For instance, if custom logic reads claims from the JWT before the built-in JWT validation runs, an attacker could supply a token with a none algorithm, an empty signature, or a mismatched key ID. Even with mTLS present, such tokens may be accepted if the application does not explicitly require and validate the JWT. Additionally, misconfigured token validation parameters, such as not setting ValidateIssuer or ValidateAudience to true, or using insecure token validation parameters like accepting unsigned tokens, can allow an attacker to forge JWTs that the server treats as valid when presented alongside a valid client certificate.
Real-world attack patterns aligned with this risk include scenarios where an API endpoint expects both mTLS and a JWT, but JWT validation is inadvertently relaxed to support legacy clients. For example, an endpoint might skip issuer validation to avoid breaking existing integrations, which effectively allows any holder of a valid client certificate to present arbitrary claims. This becomes especially critical when token introspection or revocation is not enforced, enabling the use of stolen or long-lived JWTs alongside valid certificates.
To detect this class of issue, scans review the JWT configuration in Startup or Program.cs, verify that token validation parameters such as ValidateIssuer, ValidateAudience, ValidateLifetime, and ValidateIssuerSigningKey are set to true, and confirm that the application does not make authorization decisions based on unvalidated JWT claims. The presence of mTLS is verified through server certificate configuration, but the combination with lax JWT validation is flagged as a high-risk finding because it can lead to authentication bypass despite the presence of strong transport‑layer identity checks.
Mutual Tls-Specific Remediation in Aspnet — concrete code fixes
Remediation centers on strict JWT validation combined with properly configured mTLS in ASP.NET. Ensure that the application enforces client certificate authentication and validates JWTs with secure defaults. Below are concrete code examples for Program.cs using the minimal API approach in .NET 6+.
Enabling mTLS and strict JWT validation
Configure Kestrel to require client certificates and set up JWT Bearer validation with strict parameters:
var builder = WebApplication.CreateBuilder(args);
// Enforce mTLS by requiring client certificates
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsOptions.AllowAnyClientCertificate(); // Use a custom validation callback in production
// Optionally use a custom validator:
// httpsOptions.ClientCertificateValidation = (cert, chain, errors) => ValidateClientCertificate(cert, chain, errors);
});
});
// Add authentication with strict JWT Bearer settings
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "https://your-identity-provider";
options.Audience = "api1";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://your-identity-provider",
ValidateAudience = true,
ValidAudience = "api1",
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
// Use a specific key if not discovered automatically:
// IssuerSigningKeys = new List<SecurityKey> { new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secure-key")) }
};
// Optional: configure certificate chain/policy validation
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// Ensure token is read from header or query; avoid fallback to cookies
var token = context.Request.Headers["Authorization"].ToString()?.Replace("Bearer ", "");
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return Task.CompletedTask;
}
};
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/secure", (ClaimsPrincipal user) =>
{
return Results.Ok(new { user.Identity?.Name, Scopes = user.FindFirst("scope")?.Value });
}).RequireAuthorization();
app.Run();
Key points in the configuration:
ClientCertificateMode.RequireCertificateensures mTLS is mandatory.ValidateIssuer,ValidateAudience,ValidateLifetime, andValidateIssuerSigningKeyare all set to true to enforce strict JWT validation.- Explicitly set
ValidIssuerandValidAudienceto prevent token substitution across issuers or audiences. - The
OnMessageReceivedevent is used cautiously to read the token, avoiding unintended sources such as cookies.
For production, replace AllowAnyClientCertificate with a custom validation callback that checks certificate revocation, thumbprint allowlisting, or chain validation. Additionally, avoid hardcoding secrets; use Azure Key Vault, environment variables, or secure configuration providers for keys and issuer metadata.
Testing the combined controls
Verify that endpoints reject requests with a valid JWT but an invalid or missing client certificate, and also reject requests with a valid client certificate but an invalid, expired, or misconfigured JWT. This ensures both mTLS and JWT act as complementary controls rather than one weakening the other.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |