Using client certificates in .NET part 9: working with client certificates in OWIN/Katana III

Introduction

In the previous post we added a couple of components necessary to add client certificate authentication into the OWIN middleware chain. We haven’t yet put the elements to work though. That is the main topic of this post which will finish this series. We’ll also run a couple of tests.

Have the demo application open in Visual Studio in administrator mode.

The client certificate validator

Recall that we introduced a loose dependency in the form of the IClientCertificateValidator interface. We haven’t yet seen any implementation yet. Add the following class to the Authentication folder:

public class DefaultClientCertificateValidator : IClientCertificateValidator
{
	public ClientCertificateValidationResult Validate(X509Certificate2 certificate)
	{
		bool isValid = false;
		List<string> exceptions = new List<string>();
		try
		{
			X509Chain chain = new X509Chain();
			X509ChainPolicy chainPolicy = new X509ChainPolicy()
			{
				RevocationMode = X509RevocationMode.NoCheck,
				RevocationFlag = X509RevocationFlag.EntireChain
			};
			chain.ChainPolicy = chainPolicy;
			if (!chain.Build(certificate))
			{
				foreach (X509ChainElement chainElement in chain.ChainElements)
				{
					foreach (X509ChainStatus chainStatus in chainElement.ChainElementStatus)
					{
						exceptions.Add(chainStatus.StatusInformation);
					}
				}
			}
			else
			{
				isValid = true;
			}
		}
		catch (Exception ex)
		{
			exceptions.Add(ex.Message);
		}
		ClientCertificateValidationResult res = new ClientCertificateValidationResult(isValid);
		res.AddValidationExceptions(exceptions);
		return res;
	}
}

You should be able to follow the code based on what we we saw in this post of this series.

The authentication middleware

In order to be able to follow this post you should have at least some basic knowledge of OWIN and Katana components: how they are written, what conventions they follow and how they are added to the OWIN middleware chain. As noted before, there are sources on this blog, e.g. here, that goes through OWIN from the ground up.

Authentication middleware in OWIN has a specially designed base class: AuthenticationMiddleware. It is a base class for all types of OWIN authentication middleware and has a large number of built-in implementations, e.g. CookieAuthenticationMiddleware, GoogleAuthenticationMiddleware or WsFederationAuthenticationMiddleware. AuthenticationMiddleware is a generic class with a type parameter of type AuthenticationOptions just like we saw in the case of AuthenticationHandler. We already have an implementation of AuthenticationOptions so we’ll use that of course.

AuthenticationMiddleware has two requirements for the implementing classes:

  • Override the CreateHandler method which returns an AuthenticationHandler class
  • Have a constructor which at least has two parameters: one of type OwinMiddleware and another of type AuthenticationOptions

Our implementation of CreateHandler will be very simple: we’ll just return an instance of ClientCertificateAuthenticationHandler. However, what is this OwinMiddleware class? OwinMiddleware is an abstract base class for all sorts of Owin middleware. If you’ve seen examples of other Owin middleware you’ll be aware of the “next” component. In OWIN the components are chained in some order and each component will call the “next” component in line when it is done with its logic, like in this example:

private readonly ApplicationFunction _nextComponent;

public WelcomeComponent(ApplicationFunction appFunc)
{
    if (appFunc == null) throw new ArgumentNullException("AppFunc of next component");
    _nextComponent = appFunc;
}

public async Task Invoke(IDictionary<string, object> environment)
{
    await _nextComponent(environment);
}

Add a class called ClientCertificateAuthMiddleware into the Authentication folder:

public class ClientCertificateAuthMiddleware : AuthenticationMiddleware<ClientCertificateAuthenticationOptions>
{
	private readonly IClientCertificateValidator _clientCertificateValidator;

	public ClientCertificateAuthMiddleware(OwinMiddleware nextMiddleware, ClientCertificateAuthenticationOptions authOptions,
		IClientCertificateValidator clientCertificateValidator)
		: base(nextMiddleware, authOptions)
	{
		if (clientCertificateValidator == null) throw new ArgumentNullException("ClientCertificateValidator");
		_clientCertificateValidator = clientCertificateValidator;
	}

	protected override AuthenticationHandler<ClientCertificateAuthenticationOptions> CreateHandler()
	{
		return new ClientCertificateAuthenticationHandler(_clientCertificateValidator);
	}
}

That should be fairly simple. We return a new ClientCertificateAuthenticationHandler using the incoming implementation of IClientCertificateValidator.

The last class we need to write is the application function extension. Add a class called ClientCertificateAuthenticationExtensions into the Authentication folder:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using ClientCertTests.Authentication;

namespace Owin
{
	public static class ClientCertificateAuthenticationExtensions
	{
		public static void UseClientCertificateAuthentication(this IAppBuilder appBuilder, IClientCertificateValidator clientCertificateValidator)
		{
			appBuilder.Use<ClientCertificateAuthMiddleware>(new ClientCertificateAuthenticationOptions(), clientCertificateValidator);
		}
	}
}

This is very little code but it’s worth going through some important aspects of it.

This is of course an extension method that adds functionality to IAppBuilder. We’ll be able to call appBuilder.UseClientCertificateAuthentication from Startup.cs later on which follows OWIN conventions. Other developers may be looking for a similar “Use…” extension method when they type “appBuilder.” within the Configuration method in Startup.cs.

Note that the namespace is set to OWIN so that IntelliSense can easily pick up the extension method in Startup.cs which has a “using Owin” statement.

We then tell appBuilder to use our authentication middleware ClientCertificateAuthMiddleware. This overloaded Use method accepts an object param which will be used to inject the dependencies into the constructor of ClientCertificateAuthMiddleware. We’ve seen above that ClientCertificateAuthMiddleware requires three elements in its constructor: OwinMiddleware, ClientCertificateAuthenticationOptions and an IClientCertificateValidator. We have ClientCertificateAuthenticationOptions for the second argument and an IClientCertificateValidator for the third in the call…

appBuilder.Use<ClientCertificateAuthMiddleware>(new ClientCertificateAuthenticationOptions(), clientCertificateValidator);

…but what happened to OwinMiddleware? We don’t actually need to worry about it. Every OWIN middleware has to have a constructor with the “next” parameter, e.g. the one we saw above:

public WelcomeComponent(ApplicationFunction appFunc)

That is a must and .NET will complain if it’s not present. .NET will also take care of inserting the next component for you in the constructor at runtime. OwinMiddleware is just another form of the “next” component and ClientCertificateAuthMiddleware is an OWIN component just like WelcomeComponent.

Startup

Here’s the revised Startup class:

public class Startup
{
	public void Configuration(IAppBuilder appBuilder)
	{
		appBuilder.UseClientCertificateAuthentication(new DefaultClientCertificateValidator());
	}
}

Testing

If you start the web application with the http://localhost:xxxx endpoint now and navigate to /customers you should get a 401:

Authorization has been denied for this request.

The reason is that there’s no incoming client certificate and the Authorize attribute therefore blocks access to the Get method of CustomersController.

Change the address to https://localhost:xxxx/customers and you should be prompted for the client cert to send with the request. The code should succeed and the customers list will be presented on the screen. It’s a good idea to set breakpoints in ClientCertificateAuthenticationHandler and DefaultClientCertificateValidator so that you can follow the execution chain.

Likewise, calling the customers endpoint via a web request from the console tester application should equally pass through the OWIN middleware and pass.

We have successfully implemented the basics of client certificate validation in OWIN. From the controller’s point of view all that was needed was the Authorize attribute. The rest of the authentication logic was separated out to specialised classes.

That’s it folks. I hope you’ve learnt something new about client certificates in .NET.

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

Advertisements

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

iReadable { }

.NET Tips & Tricks

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

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

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: