Self Hosted Asp Net Core Web server, client authentication with self-signed certificates

832 Views Asked by At

I am testing a self-hosted Asp Net Core Web server (Kestrel), and I am struggling with the client authentication using self-signed certificates. This is my startup code

WebApplicationBuilder webBuilder = WebApplication.CreateBuilder();
var webHostBuilder = builder.WebHost;

X509Certificate2 rootCert = new X509Certificate2(hostCertFilePath, hostCertPassword);

webHostBuilder.ConfigureKestrel(o =>
{
    o.ConfigureHttpsDefaults(o =>
    {
        o.ServerCertificate = rootCert;
        o.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
    });
});

webHostBuilder.UseKestrel(o =>
{
    o.Listen(IPAddress.Parse(myHttpsEndPointIpAddr), myHttpsEndPointPort,
        listenOptions =>
        {
            listenOptions.UseHttps();
        });
    o.Listen(IPAddress.Parse(myHttpEndPointIpAddr), myHttpEndPointPort);
});

var services = webBuilder.Services;

services.AddTransient<MyCustomCertificateValidationService>();
services
    .AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                var validationService = context.HttpContext.RequestServices
                    .GetService<MyCustomCertificateValidationService>();

                if (validationService.ValidateCertificate(context.ClientCertificate))
                {
                    context.Success();
                }
                else
                {
                    context.Fail("invalid cert");
                }

                return Task.CompletedTask;
            },
            OnAuthenticationFailed = context =>
            {
                context.Fail("invalid cert");
                return Task.CompletedTask;
            }
        };
    });

...

var app = webBuilder.Build();

app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

This my custom certification class

public class MyCustomCertificateValidationService
{
    public bool ValidateCertificate(X509Certificate2 clientCertificate)
    {
        // todo: check certificate thumbnail
        return false;
    }
}

But even if MyCustomCertificateValidationService has a method ValidateCertificate() that returns false, the controller method is still called when a client accesses the url with the route to the controller method. This is what is displayed in the log:

...
AspNetCore.Routing.EndpointRoutingMiddleware : Request matched endpoint ‘GetMyData…‘
AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler : Certificate was not authenticated. Failure message: invalid cert
AspNetCore.Routing.EndpointMiddleware : Executing endpoint ‘GetMyData…‘
...

Any clue why the controller method is still called?

1

There are 1 best solutions below

0
On

"There is a use-case for the application that in some test environment also unauthorized calls (over http://...) should be allowed. I would prefer to use, if possible, a settings parameter to decide dynamically if http access is allowed or not, instead of "hardcode" it as [Authorize] attribute"

Of course you can do that. There is a handy way to implement your requirement using middleware for sure. Please try the code snippe below:

Http/Https Request Middleare Based On Environment:

public class CustomHttpHttpsRequestMiddleware
    {
        private readonly RequestDelegate next;
        public CustomHttpHttpsRequestMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
                //env = "Production";
            if (env == "Development")
            {
                await next(context);
            }
            else
            {
                if (!context.Request.IsHttps)
                {
                    context.Response.StatusCode = StatusCodes.Status400BadRequest;
                    await context.Response.WriteAsync("HTTPS required!");
                }
            }


        }
    }

Note: In application request context we are checking two important value, first if the request is secure means cIsHttps and the application environment, in Development environment we will allow http request. Therefore, other than, dev or any env based on our requirement we will reject http request.

Register Middleware on Program.cs:

app.UseMiddleware<CustomHttpHttpsRequestMiddleware>();

Note: Make sure you have followed the correct middleware order. In order to avoid short circuiting, you could place this middleware way down of your all current middleware.

Output:

enter image description here