Client Certificate Authentication Middleware for Katana

Katana has no middleware to turn SSL client certificates into a ClaimsIdentity. And since I am currently collecting material for my upcoming Web API security course I used the opportunity to experiment with Katana authentication middleware.

There’s a certain pattern you have to follow for integrating into the Katana security model. It all starts with a class that configures the authentication mechanism – the so called options:

public class ClientCertificateAuthenticationOptions : AuthenticationOptions

{

    public X509CertificateValidator Validator { get; set; }

    public bool CreateExtendedClaimSet { get; set; }

 

    public ClientCertificateAuthenticationOptions() : base(“X.509”)

    {

        Validator = X509CertificateValidator.ChainTrust;

        CreateExtendedClaimSet = false;

    }

}

You have to derive from AuthenticationOptions and set your default authentication type – that’s just a string and can be changed by the user of your middleware. You further expose whatever customization make sense for you – in my case the certificate validator and an indicator if you want a minimal or a full claim set.

The real work is done in a so called authentication handler – again there is base class to derive from. You typically need to implement AuthenticateCoreAsync (credential validation) and ApplyResponseChallengeAsync (challenge generation). Client certificates don’t really have an HTTP challenge – so I skipped this method. There are other methods that you can override when you need initialization and cleanup features (InitializeCoreAsync and TeardownCoreAsync).

In the authentication logic, you inspect the incoming credential and – if present – turn it into a ClaimsIdentity. This identity in turn gets wrapped into an authentication ticket which allows to couple the identity with additional properties that are not claims. –

public class ClientCertificateAuthenticationHandler :
  AuthenticationHandler
<ClientCertificateAuthenticationOptions
>

{

    protected override Task<AuthenticationTicket> AuthenticateCoreAsync()

    {

        var cert = Context.Get<X509Certificate2>(“ssl.ClientCertificate”);

 

        if (cert == null)

        {

            return Task.FromResult<AuthenticationTicket>(null);

        }

 

        try

        {

            Options.Validator.Validate(cert);

        }

        catch

        {

            return Task.FromResult<AuthenticationTicket>(null);

        }

 

        var claims = GetClaimsFromCertificate(
          cert, cert.Issuer, Options.CreateExtendedClaimSet);

 

        var identity = new ClaimsIdentity(Options.AuthenticationType);

        identity.AddClaims(claims);

 

        var ticket = new AuthenticationTicket(
          identity,
new AuthenticationProperties
());

        return Task.FromResult<AuthenticationTicket>(ticket);

    }

}

 

The glue between the handler, lifetime management, configuration and the Katana pipeline is the Katana authentication middleware. It’s responsibility is to create the handler, transfer the options and call the corresponding methods on the handler at the right point in time:

public class ClientCertificateAuthenticationMiddleware :
  AuthenticationMiddleware
<ClientCertificateAuthenticationOptions
>

{

    public ClientCertificateAuthenticationMiddleware(
     
OwinMiddleware next,
     
ClientCertificateAuthenticationOptions
options)

        : base(next, options)

    { }

 

    protected override AuthenticationHandler<ClientCertificateAuthenticationOptions> CreateHandler()

    {

        return new ClientCertificateAuthenticationHandler();

    }

}

…and with a bit of extension method syntactic sugar – you end up here:

app.UseClientCertificateAuthentication();

app.UseWebApi(WebApiConfig.Register());

 

The claims describing the client certificate can then be retrieved either using the OwinContext or Web API specific using the RequestContext.

This entry was posted in IdentityModel, Katana, OWIN, WebAPI. Bookmark the permalink.

5 Responses to Client Certificate Authentication Middleware for Katana

  1. Nico says:

    Hello, I’m a rookie in the world of WEB API and Katana. I’ve just tried to implement this source code and added several breakpoints in both ClientCertificateAuthenticationHandler and ClientCertificateAuthenticationMiddleware but I’m not breaking. I’ve added “app.UseClientCertificateAuthentication();” and ensured that I’m breaking in this one. Any thoughts?

  2. Nico says:

    In fact, I had to add my own ClientCertificateAuthenticationExtensions as follows:

    public static class ClientCertificateAuthenticationExtensions
    {
    public static IAppBuilder UseClientCertificateAuthentication(this IAppBuilder app, X509RevocationMode revocationMode = X509RevocationMode.Online, bool createExtendedClaims = false)
    {
    X509CertificateValidator chainTrustValidator = X509CertificateValidator.CreateChainTrustValidator(true, new X509ChainPolicy()
    {
    RevocationMode = revocationMode
    });
    ClientCertificateAuthenticationOptions options = new ClientCertificateAuthenticationOptions(“X.509”)
    {
    Validator = chainTrustValidator,
    CreateExtendedClaimSet = createExtendedClaims
    };
    return ClientCertificateAuthenticationExtensions.UseClientCertificateAuthentication(app, options);
    }

    public static IAppBuilder UseClientCertificateAuthentication(this IAppBuilder app, ClientCertificateAuthenticationOptions options)
    {
    AppBuilderUseExtensions.Use(app, new object[1]
    {
    (object) options
    });
    return app;
    }
    }

    and then, use it by calling ClientCertificateAuthenticationOptions with the constructor with the “string authenticationType” as follows:

    public class ClientCertificateAuthenticationOptions : AuthenticationOptions
    {

    public X509CertificateValidator Validator { get; set; }
    public bool CreateExtendedClaimSet { get; set; }

    public ClientCertificateAuthenticationOptions(string authenticationType)
    : base(“X.509”)
    {
    Validator = X509CertificateValidator.None;
    CreateExtendedClaimSet = false;
    }
    }

    Witthout doing that, the code I wrote was never used (instead, the Thinktecture code was used). I guess that by default thinktecture was using a validator in “Chain trust” mode and as I use a self-signed certificate on my dev. workstation, I guess it had no chance to work because of an error of this kind:

    “error”:{
    “code”:””,”message”:”Authorization has been denied for this request.”

    => {System.IdentityModel.Tokens.SecurityTokenValidationException: The X.509 certificate CN=10.131.215.199 is not in the trusted people store. The X.509 certificate CN=10.131.215.199 chain building failed. The certificate that was used has a trust chain that cannot be verified. Replace the certificate or change the certificateValidationMode. The revocation function was unable to check revocation for the certificate.

    at System.IdentityModel.Selectors.X509CertificateValidator.PeerOrChainTrustValidator.Validate(X509Certificate2 certificate)
    at AIT.AITPortal.Server.Plugins.QueryController.ClientCertificateAuthenticationHandler.AuthenticateCoreAsync() in c:\dev\xTaT\AIT Portal\Version2\RoutingEngine\Server\Plugins\QueryControler\QueryController.cs:line 643}

    So now I have my own classes as above and one overriding ClientCertificateAuthenticationExtensions with a “debug mode” in which I use “X509CertificateValidator.None” for debug purpose.

    Does it make sense to override this class “ClientCertificateAuthenticationExtensions” or is there another proper mean for solving this issue?

  3. Hi! I’m trying to implement this but I’m struggling to get the certificate from Context.
    The “Context.Get(“ssl.ClientCertificate”);” returns always a null value.

    I’m doing the request from SignalR like this:

    var connection = new HubConnection(“https://localhost:8080/”);
    connection.AddClientCertificate(X509Certificate.CreateFromCertFile(“c1.cer”));
    IHubProxy myHub = connection.CreateHubProxy(“MyHub”);
    connection.Start().Wait();

    Do you have any clue why I can’t get the certificate?
    Thanks

  4. Bra says:

    Hi Dominic
    I have applied this code, actually all the classes from your Web API 2 security course on Pluralsight, on my self hosted project and when I call a method without a certificate the code flow is as it should be but I still get a regular response back from the server.
    Is there some code still missing still?

Leave a comment