How to Use a Client Certificate to Authenticate and Authorize in a Web API

How to use a client certificate to authenticate and authorize in a Web API

Tracing helped me find what the problem was (Thank you Fabian for that suggestion). I found with further testing that I could get the client certificate to work on another server (Windows Server 2012). I was testing this on my development machine (Window 7) so I could debug this process. So by comparing the trace to an IIS Server that worked and one that did not I was able to pinpoint the relevant lines in the trace log. Here is a portion of a log where the client certificate worked. This is the setup right before the send

System.Net Information: 0 : [17444] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded).
System.Net Information: 0 : [17444] SecureChannel#54718731 - We have user-provided certificates. The server has not specified any issuers, so try all the certificates.
System.Net Information: 0 : [17444] SecureChannel#54718731 - Selected certificate:

Here is what the trace log looked like on the machine where the client certificate failed.

System.Net Information: 0 : [19616] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded).
System.Net Information: 0 : [19616] SecureChannel#54718731 - We have user-provided certificates. The server has specified 137 issuer(s). Looking for certificates that match any of the issuers.
System.Net Information: 0 : [19616] SecureChannel#54718731 - Left with 0 client certificates to choose from.
System.Net Information: 0 : [19616] Using the cached credential handle.

Focusing on the line that indicated the server specified 137 issuers I found this Q&A that seemed similar to my issue. The solution for me was not the one marked as an answer since my certificate was in the trusted root. The answer is the one under it where you update the registry. I just added the value to the registry key.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL

Value name: SendTrustedIssuerList Value type: REG_DWORD Value data: 0 (False)

After adding this value to the registry it started to work on my Windows 7 machine. This appears to be a Windows 7 issue.

SSL Client Authentication with certificate using c#

Some time ago I've created this POC for client authentication with certificate in .Net Core. It uses idunno.Authentication package that is now build-in in .Net Core. My POC probably is bit outdated now, but it can be a good starting point for you.

First create an extension method to add certificate to HttpClientHandler:

public static class HttpClientHandlerExtensions
{
public static HttpClientHandler AddClientCertificate(this HttpClientHandler handler, X509Certificate2 certificate)
{
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(certificate);

return handler;
}
}

Then another extension method to add certificate to IHttpClientBuilder

    public static IHttpClientBuilder AddClientCertificate(this IHttpClientBuilder httpClientBuilder, X509Certificate2 certificate)
{
httpClientBuilder.ConfigureHttpMessageHandlerBuilder(builder =>
{
if (builder.PrimaryHandler is HttpClientHandler handler)
{
handler.AddClientCertificate(certificate);
}
else
{
throw new InvalidOperationException($"Only {typeof(HttpClientHandler).FullName} handler type is supported. Actual type: {builder.PrimaryHandler.GetType().FullName}");
}
});

return httpClientBuilder;
}

Then load the certificate and register HttpClient in HttpClientFactory

        var cert = CertificateFinder.FindBySubject("your-subject");
services
.AddHttpClient("ClientWithCertificate", client => { client.BaseAddress = new Uri(ServerUrl); })
.AddClientCertificate(cert);

Now when you will use client created by factory it will automatically send your certificate with the request;

public async Task SendRequest()
{
var client = _httpClientFactory.CreateClient("ClientWithCertificate");
....
}

Client Authentication for WebAPI 2

This is a very "tricky" options for authenticating and authorizing clients. It's very powerful, but could be very expensive to implement because you have to manage a full PKI (public key infrastructure) and you have to distribute securely the certificats to your clients.

1) You need SSL in place and you need to enforce it (even globally if you want):

public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "HTTPS Required"
};
}
else
{
base.OnAuthorization(actionContext);
}
}
}

public class ValuesController : ApiController
{
[RequireHttps]
public HttpResponseMessage Get() { ... }
}

2) You need to configure IIS to accept client certificates througt the application.host config or using the IIS manager console:

<system.webServer>
<security>
<access sslFlags="Ssl, SslNegotiateCert" />
<!-- To require a client cert: -->
<!-- <access sslFlags="Ssl, SslRequireCert" /> -->
</security>
</system.webServer>

3) On the server side, you can get the client certificate by calling GetClientCertificate on the request message. The method returns null if there is no client certificate. Otherwise, it returns an X509Certificate2 instance. Use this object to get information from the certificate, such as the issuer and subject. Then you can use this information for authentication and/or authorization.

X509Certificate2 cert = Request.GetClientCertificate();
string issuer = cert.Issuer;
string subject = cert.Subject;

Check this article of Mike Watson for full reference (I gave you an extract here).

is this a solid approach?

Yes it is, but as you saw as the PKI drawback to keep in mind. Eventually you can implement OAuth2 auth which is also extremely powerful and you can easely base it on an external provider, for exaple Azure AD. Check this article for more details. BTW, you can also start from the basic MVC/API template.

.Net Core Web API with Client Certificate Authentication

For proper certificate authentication using the ASP.NET Core authentication stack, you can also check out idunno.Authentication.Certificate by Barry Dorrans himself. It allows you to enable certificate authentication for your application and handles it like any other authentication scheme, so you can keep actual certificate-based logic out of your business logic.

This project sort of contains an implementation of Certificate Authentication for ASP.NET Core. Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal.

You must configure your host for certificate authentication, be it IIS, Kestrel, Azure Web Applications or whatever else you're using.

Make sure to also check out the “documentation” on how to set this up properly, since it requires configuration of the host to work properly, just like you did with IIS Express. Instructions for other servers like raw Kestrel, IIS, Azure or general reverse proxies are included.



Related Topics



Leave a reply



Submit