I am trying to make a GET
request by certificate or JWT to the same controller endpoint, but it always seems to trigger the certificate one.
My JWT authentication looks currently like that:
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = builder.Configuration["Jwt:Issuer"];
options.Audience = builder.Configuration["Jwt:Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
// Configure token validation parameters
IncludeTokenOnFailedValidation = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ClockSkew = TimeSpan.Zero,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
// Add custom claims to the principal if the token is valid
var claims = new[]
{
new Claim(
ClaimTypes.NameIdentifier,
context.Scheme.Name,
ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(ClaimTypes.Role, "Claim_AdminPrivilege_API_jwt_Read")
};
context.Principal = new ClaimsPrincipal(
new ClaimsIdentity(claims, context.Scheme.Name));
// Optionally, you can access and modify other properties of the context here
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
And my certificate looks like this:
builder.Services.AddSingleton<CertificateValidationService>();
// Certificate authentication in ASP.NET Core
builder.Services
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.RevocationMode = X509RevocationMode.NoCheck;
options.AllowedCertificateTypes = CertificateTypes.All;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices.GetService<CertificateValidationService>();
string certificate = builder.Configuration.GetSection("Certificate").GetValue<string>("File") ?? string.Empty;
string key = builder.Configuration.GetSection("Certificate").GetValue<string>("Key") ?? string.Empty;
if (validationService!= null && validationService.ValidateCertificate(context.ClientCertificate, certificate, key))
{
Console.WriteLine("Success");
var claims = new[]
{
new Claim(
ClaimTypes.NameIdentifier,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(ClaimTypes.Role, "Claim_AdminPrivilege_API_Read")
};
context.Principal = new ClaimsPrincipal(
new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
else
{
Console.WriteLine("invalid cert");
context.Fail("invalid cert");
}
return Task.CompletedTask;
}
};
});
In both cases I build the claim that later I am using for the policy:
options.AddPolicy("Role_AdminPrivilege_API", p => {
// Add the authentication scheme for certificate
//p.AuthenticationSchemes.Add(CertificateAuthenticationDefaults.AuthenticationScheme);
p.AddAuthenticationSchemes(CertificateAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme);
//p.RequireAssertion(context => true);
//p.RequireClaim(ClaimTypes.Role, "Claim_AdminPrivilege_API_Read");
p.RequireAssertion(context =>
context.User.HasClaim(c => c.Type == ClaimTypes.Role &&
(c.Value == "Claim_AdminPrivilege_API_Read" || c.Value == "Claim_AdminPrivilege_API_jwt_Read") ));
});
but this policy seems always just to use the certificate authentication method unless I create second function with another policy only for JWT.
When I call the web request with the JWT token I get the error:
Interop+OpenSsl+SslException: Operation failed with error - 14.
at System.Net.Security.SslStream.RenegotiateAsync[TIOAdapter](CancellationToken cancellationToken)
This is my controller function:
//[Authorize(Roles = "Claim_AdminPrivilege_API_Read,Claim_AdminPrivilege_API_jwt_Read")]
[Authorize(Policy = "Role_AdminPrivilege_API_Read")]
[HttpGet("GetEndpointAdmin/{hostname}")]
public async Task<IActionResult> GetEndpointAdmin(string hostname)
I also never see that the JWT authentication method gets triggered.
Is this idea, having two different authentications working with a same policy, even possible or do I have to create GetEndpointAdminJwt
function with its own policy?
Thanks
Not sure if it is maybe related to the Kestrl server:
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.ConfigureHttpsDefaults(options =>
{
options.AllowAnyClientCertificate();
options.CheckCertificateRevocation = false;
//options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
});
});
But when I say no NoCertificate Jwt works, but of course certificate is not working anymore at all.
Edit:
Checking the header on the OnAuthenticationFailed in the CertificateAuthenticationEvents event shows that the header of jwt is Bearer but still gets forwarded to the certificate section.