Using client certificates in .NET part 5: working with client certificates in a web project

Introduction

In the previous post we looked at a couple pf examples on how to work with digital certificates in C# code. In particular we saw how to load certificates from a certificate store, how to search for and how to validate one.

In this post we’ll go through how to attach a client certificate to a web request and how to extract it in a .NET Web API 2 project.

Web API 2 project preparations

Open Visual Studio 2012 or higher. You’ll need to create a Web API 2 project and there are a number of different ways. It depends on the version of Visual Studio you have and the templates that are installed. You can create an ASP.NET Web Application in Visual Studio 2013 and select the Web API template in the “New ASP.NET Project” dialog. I’ll go for the empty Web API 2 project type in Visual Studio 2012:

Empty Web API 2 project

However, it doesn’t really make a difference how you create a Web API 2 project. Note that the template type shown above may not be available for you. You can download the necessary extension from MSDN here.

As promised the template creates an empty Web API 2 project:

Empty Web API 2 project created

There’s one change I’d like to make already now however. WebApiConfig.cs in the App_Start defines the route template with the “api” prefix:

routeTemplate: "api/{controller}/{id}"

That may be necessary if you have a mixed Web API and MVC project with web and API controllers. In this case however it has no extra value so I’ll remove the “api” bit:

routeTemplate: "{controller}/{id}"

Let’s test to add a controller. Right-click the Controllers folder and click Add, Controller… and select the Web API 2 Controller – Empty scaffold type:

Add empty web api 2 controller

Call it CustomersController. The template will add a very empty Customers controller as promised:

public class CustomersController : ApiController
{
}

Add the following Get action method:

public IHttpActionResult Get()
{
	IList<Customer> customers = new List<Customer>();
	customers.Add(new Customer() { Name = "Nice customer", Address = "USA", Telephone = "123345456" });
	customers.Add(new Customer() { Name = "Good customer", Address = "UK", Telephone = "9878757654" });
	customers.Add(new Customer() { Name = "Awesome customer", Address = "France", Telephone = "34546456" });
	return Ok<IList<Customer>>(customers);
}

…where Customer is located in the Models folder and has the following properties:

public class Customer
{
    public string Name { get; set; }
    public string Address { get; set; }
    public string Telephone { get; set; }
}

Run the application and call the /customers extension in the browser. In Chrome you should see the list of customers in XML format:

Array of customers shown in chrome as xml

Nice, we’ve completed the preparation phase. You can set a breakpoint in the beginning of the Get method already now for the next step.

Calling the Web API project from an external caller

We’ll call the GET customers action from a console application. Right-click the solution and select Add, New Project and then search for the Console Application project type. Give it some name, e.g. ClientCaller. We’ll need to reference the following libraries in the console app:

Necessary libraries to make http calls

Add the following code to Program.cs:

class Program
{
	static void Main(string[] args)
	{
		HttpClient client = new HttpClient()
		{
			BaseAddress = new Uri("http://localhost:3020/")
		};

		HttpResponseMessage response = client.GetAsync("customers").Result;
		string responseContent = response.Content.ReadAsStringAsync().Result;
		Console.WriteLine(responseContent);
		Console.ReadKey();
	}
}

You’ll need to adjust the port number of course. You can read it from the address you see in the browser when starting the Web API project. I trust that you understand the usage of the HttpClient object.

Run the web project first as normal. Then right-click the console application, select Debug, Start New Instance. You should see that the web api endpoint is indeed called and the list of customers is returned:

List of customers retrieved from web api endpoint

Great, we now have the necessary components to build upon.

Attaching the client certificate to the web request

First let’s extend Program.cs of the console application with a function that locates our test client certificate:

private static X509Certificate2 GetClientCertificate()
{
	X509Store userCaStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
	try
	{
		userCaStore.Open(OpenFlags.ReadOnly);
		X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
		X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindBySubjectName, "localtestclientcert", true);
		X509Certificate2 clientCertificate = null;
		if (findResult.Count == 1)
		{
			clientCertificate = findResult[0];
		}
		else
		{
			throw new Exception("Unable to locate the correct client certificate.");
		}
		return clientCertificate;
	}
	catch
	{
		throw;
	}
	finally
	{
		userCaStore.Close();
	}
}

You can probably follow that code based on what we saw before.

Next we’ll extend Main. The WebRequestHandler object in the System.Net.Http namespace allows us to attach a client certificate to the web request. The handler is then supplied to the HttpClient:

static void Main(string[] args)
{
	try
	{
		X509Certificate2 clientCert = GetClientCertificate();
		WebRequestHandler requestHandler = new WebRequestHandler();
		requestHandler.ClientCertificates.Add(clientCert);

		HttpClient client = new HttpClient(requestHandler)
		{
			BaseAddress = new Uri("http://localhost:3020/")
		};

		HttpResponseMessage response = client.GetAsync("customers").Result;
		response.EnsureSuccessStatusCode();
		string responseContent = response.Content.ReadAsStringAsync().Result;
		Console.WriteLine(responseContent);		
	}
	catch (Exception ex)
	{
		Console.WriteLine("Exception while executing the test code: {0}", ex.Message);
	}
        Console.ReadKey();
}

That was easy, right?

The next logical step is to read the client certificate from the web request in our Web API controller. Let’s say that only those callers with a client certificate are allowed to view the customers’ list. The Request and the RequestContext properties that are available within a controller both have suitable extensions. The following code shows both but one of them is commented out:

public class CustomersController : ApiController
{
	public IHttpActionResult Get()
	{			
		X509Certificate2 clientCertInRequest = RequestContext.ClientCertificate;
		//X509Certificate2 clientCertInRequest = Request.GetClientCertificate();
		if (clientCertInRequest != null)
		{
			IList<Customer> customers = new List<Customer>();
			customers.Add(new Customer() { Name = "Nice customer", Address = "USA", Telephone = "123345456" });
			customers.Add(new Customer() { Name = "Good customer", Address = "UK", Telephone = "9878757654" });
			customers.Add(new Customer() { Name = "Awesome customer", Address = "France", Telephone = "34546456" });
			return Ok<IList<Customer>>(customers);
		}
		AuthenticationHeaderValue authHeaderValue = new AuthenticationHeaderValue("ClientCert");
		return Unauthorized(authHeaderValue);
	}
}

We don’t perform any certificate validation yet, we’ll do that later. For the time being we want to make sure that we can read the incoming client certificate. You can place a breakpoint within the above Get method for easier debugging.

Run the web app and the console like we did before. You’ll see that clientCertInRequest is …err… null. What happened? Where did our client certificate go?

Let’s see. We mentioned a couple of things in the first post of this series that make more sense now. First this thing with IIS:

Client certificate options in IIS

We run the web application in IIS Express of Visual Studio which is obviously not the same as a “normal” IIS installation. So we cannot just simply open IIS and modify this setting for our local demo. However, the above above picture suggests that by default client certificates are ignored. The client certificate is unfortunately ignored when we send it to the customers controller.

Oh well, let’s try something different then. That first post also mentioned an extension to the config file. Open web.config and add the following bit of XML within the system.webServer node:

<security>
	<access sslFlags="SslNegotiateCert" />
</security>

Run the web application now. What do we have now? HTTP Error 500.19 – Internal Server Error with the following config error:

This configuration section cannot be used at this path. This happens when the section is locked at a parent level. Locking is either by default (overrideModeDefault=”Deny”), or set explicitly by a location tag with overrideMode=”Deny” or the legacy allowOverride=”false”.

Setting the access flag like we did is obviously not the right way either.

There seems to be no easy way to configure client certificates for local tests unfortunately. You can search for “iis express client certificate” and similar terms on the Internet and you’ll find a couple of possible workarounds with varying complexity. I think they are all too involved to achieve a simple purpose so we’ll try something different in the next post.

You can view the list of posts on Security and Cryptography here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

9 Responses to Using client certificates in .NET part 5: working with client certificates in a web project

  1. Pingback: How to Secure your Azure API Management Infrastructure – Nigel Wardle

  2. Dominik says:

    Thanks for this excellent walk through.

    For the benefit of those still wishing to use IIS Express: A relative simple way to make IIS Express work with client certificates is described here
    https://improveandrepeat.com/2017/07/how-to-configure-iis-express-to-accept-ssl-client-certificates/
    It requires just a tiny tweak to the applicationhost.config, which I did not find to involved.

    • Eric says:

      Hi Dominik,

      Do you know of a way to make a self-hosted full .net web api work with client certificates? will the sent certificate be null by default?

      Thank you.

  3. thank says:

    Thank!

  4. Eric says:

    Hi Andras, thanks for your article, I also asked Dominik above, but will ask you as well…

    Do you know a way to make a self-hosted full .net web api work with client certificates? will the sent certificate be null by default?

    Thank you.

  5. Pingback: Using Chained Certificates for Certificate Authentication in ASP.NET Core 3.0 | Software Engineering

  6. Michael says:

    Thank you for posting your series of articles. I’ve found them to be very useful, again thanks!

  7. viki says:

    thanks for nice post

  8. mattfrear says:

    Thanks for this.

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.