I'm trying to implement Certificate based authentication with ASP.NET Core 9 following the official documentation: Configure certificate authentication in ASP.NET Core
I've come up with this basic implementation:
using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.ConfigureHttpsDefaults(options =>
{
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
});
builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.AllowedCertificateTypes = CertificateTypes.All;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
Console.WriteLine($"Validating: {context.ClientCertificate.Subject}");
if (context.ClientCertificate.Subject == "Pragmateek")
context.Success();
else
context.Fail("Invalid certificate.");
return Task.CompletedTask;
}
};
});
var app = builder.Build();
app.UseAuthentication();
app.MapGet("/", () => "Hello World!");
app.Run();
When requesting the resource from Chrome I get a popup asking me to choose a certificate, I choose one generated with New-SelfSignedCertificate -Subject "CN=Pragmateek" -CertStoreLocation cert:\CurrentUser
, but I get a ERR_BAD_SSL_CLIENT_AUTH_CERT
error.
And the validation code in OnCertificateValidated
is never called.
If it matters the project was created with the basic template dotnet new web --name ...
and is run with dotnet run --launch-profile https
(and running it with Visual Studio does not change anything).
What plumbing is missing?
The issue is that Kestrel seems to use a default client certificate validation which rejects the certificates, probably because they are self-signed.
The solution was to allow any certificate with options.AllowAnyClientCertificate();
which overwrites ClientCertificateValidation
with a truthy callback:
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.ConfigureHttpsDefaults(options =>
{
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
options.AllowAnyClientCertificate();
});
});
EDIT: another way of having the certificate accepted is to trust it by adding it to the Windows Trust Store "Trusted Root Certification Authorities":
Move-Item cert:\CurrentUser\My\B45606C0ACC9365D12172AF3E0C54BAAAAA27833 cert:\CurrentUser\Root