Azure API Management - Securing a Web API hosted as an Azure Web App using client certificates

Azure Api Management acts as a security proxy to 1 or more web services (hosted separately). The intention is that developers will request resources via Azure API Management that will forward the request onto the appropriate web API given appropriate permissions. It is important that the underlying Web Service cannot be accessed directly by an end user (and therefore bypassing the API management security).  To achieve this we are using a client certificate to validate that the request has come from the API management site.

clip_image002

This post describes how to:

  1. Create a self signed certificate
  2. Configure certificates in Azure API Management
  3. Configure the Azure Web App to enable client certificates
  4. Add code to validate a certificate has been provided

1) Create a self signed certificate

Run the following example commands to create a self-signed certificate. Tweak the values as required:

makecert.exe -n "CN=Your Issuer Name" -r -sv TempCA.pvk TempCA.cer

makecert.exe -pe -ss My -sr CurrentUser -a sha1 -sky exchange -n "CN=Your subject Name" -eku 1.3.6.1.5.5.7.3.2 -sk SignedByCA -ic TempCA.cer -iv TempCA.pvk

2) Configure certificates in Azure API Management

-> Open the Azure API management portal
-> Click APIs –> Choose the appropriate API that you want to secure -> Security -> Manage Certificates
-> Upload the certificate

clip_image002

A policy should automatically have been added that intercepts requests and appends the appropriate certificate information before forwarding the request to that Web API. Check the policies section to confirm it has been added. The following screenshot shows the expected policy definition

image

3) Configure the Azure Web App to enable client certificates

Given the Web Api is deployed as an azure App then there is no direct access to IIS to enable client certificate security. Instead configuration must be done either using the Azure REST api; or using the Azure Resource Explorer (preview).

A description of using the REST api is here.

To update is via resource explorer follow these steps:

  • go to https://resources.azure.com/, and log in as you would to the Azure portal
  • find the relevant site, either using the search box or by navigating the tree
  • Switch mode from ‘Read Only’ to ‘Read/Write’
  • click the Edit button
  • Set "clientCertEnabled": true
  • Click the PUT button at the top

4) Add some code to the web api to check the client certificate

This can be done a number of ways. However the following code will perform these checks:

  • · Check time validity of certificate
  • · Check subject name of certificate
  • · Check issuer name of certificate
  • · Check thumbprint of certificate
1
2
3  public  class BasicCertificateValidator : IValidateCertificates     { public  bool IsValid(X509Certificate2 certificate)         { if (certificate \==  null) return  false; string issuerToMatch \=  "CN=Your Issuer Name"; string subjectToMatch \=  "CN=Your subject Name"; string certificateThumbprint \=  "thumbprintToIndentifyYourCertificate"; // 1. Check time validity of certificateTimeZoneInfo myTimeZone \= TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");             var now \= TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, myTimeZone);             DateTime notBefore \= certificate.NotBefore;             DateTime notAfter \= certificate.NotAfter; if (DateTime.Compare(now, notBefore) <  0  && DateTime.Compare(now, notAfter) \>  0) return  false; // 2. Check subject name of certificate  if (!certificate.Subject.Contains(subjectToMatch)) return  false; // 3. Check issuer name of certificate  if (!certificate.Issuer.Contains(issuerToMatch)) return  false; // 4. Check thumprint of certificate  if (!certificate.Thumbprint.Trim().Equals(certificateThumbprint, StringComparison.InvariantCultureIgnoreCase)) return  false; return  true;         } public IPrincipal GetPrincipal(X509Certificate2 certificate2)         { return  new GenericPrincipal(new GenericIdentity(certificate2.Subject), new\[\] { "User" });         }             }

To check on each request to the Web Api add a custom DelegatingHandler. Extend a class from from System.Net.Http.DelegatingHandler and override the SendAsync Message. To access the certificate information you can query the HTTPRequestMessage

1
2
3  public  class CertificateAuthHandler : DelegatingHandler     { public IValidateCertificates CertificateValidator { get; set; } public CertificateAuthHandler()         {             CertificateValidator \=  new BasicCertificateValidator();         } protected  override Task<HttpResponseMessage\> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)         {             X509Certificate2 certificate \= request.GetClientCertificate(); if (certificate \==  null  ||  !CertificateValidator.IsValid(certificate))             { return Task<HttpResponseMessage\>.Factory.StartNew(() \=> request.CreateResponse(HttpStatusCode.Unauthorized));              }             Thread.CurrentPrincipal \= CertificateValidator.GetPrincipal(certificate); return  base.SendAsync(request, cancellationToken);         }     }

To add the custom message handler to all new requests add the following code to App_Start/WebApiConfig.cs

1
2
3GlobalConfiguration.Configuration.MessageHandlers.Add(new CertificateAuthHandler());

Happy Coding!

Jon