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.
This post describes how to:
- Create a self signed certificate
- Configure certificates in Azure API Management
- Configure the Azure Web App to enable client certificates
- 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
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
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