Dominic M.
Dominic M.

Reputation: 113

How to use (Microsoft.AspNetCore.TestHost)TestServer with Client Certificate

Enabling certificate authentication in .net core api causes the TestServer to always return 403-Forbidden in integration tests (despite certificate used in request).

I tried altering the CertificateValidationService in TestClientProvider but it seems the certificate validation is failing before reaching the user-defined authentication logic. The service is working properly with client certificate when deployed in Azure.

Am I missing something? Is there any way to use TestServer with an API protected by client certificate?

To Reproduce

  1. Enable certificate authentication in .NET Core API https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-3.1
// Startup.cs
    ...
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<CertificateValidationService>();
        services.AddAuthentication(
            CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(options =>
        {
            ...
        });
        services.AddAuthorization();
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
    {
        ...

        app.UseRouting();

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

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
  1. Use "authorize" attribute on your api
// WeatherForecastController.cs  (VS 2019 template)
    [ApiController]
    [Route("api/weather")]
    [Authorize]
    public class WeatherForecastController : ControllerBase
    {
    ...
    }
  1. Write integration test using Microsoft.AspNetCore.TestHost
// IntegrationTests/ClientCertificateTests.cs
    [Fact]
    public async void GivenValidCertificate_PerformGet_ExpectSuccess()
    {
        X509Certificate2 validClientCertificate;
        using (var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
        {
            certStore.Open(OpenFlags.ReadOnly);
            validClientCertificate = certStore.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true)[0];
        }

        using (var server = new TestClientProvider(ignoreCertificate: false).Server)
        {
            // Act
            var result = await server.SendAsync(context =>
            {
                context.Connection.ClientCertificate = validClientCertificate;
                context.Request.Method = "GET";
                context.Request.Path = "/api/weather";
            });

            // Assert
            Assert.Equal(200, result.Response.StatusCode);
        }
    }

Upvotes: 5

Views: 3190

Answers (1)

Dominic M.
Dominic M.

Reputation: 113

According to https://github.com/dotnet/aspnetcore/issues/18177 , I also had to to set the request scheme to "https", which worked for me.

    var result = await server.SendAsync(context =>
    {
        context.Connection.ClientCertificate = validClientCertificate;
        context.Request.Method = "GET";
        context.Request.Scheme = "https";
        context.Request.Path = "/api/weather";
    });

Upvotes: 5

Related Questions