TLS Mutual Authentication on Azure App Service with Node.js

This is the second post of a blog series regarding ‘Microservices with Node.js, TypeScript and Microsoft Azure’. In this series we will keep you up with different parts of this topic. (First Post / Third Post)
Motivation
There are multiple authentication methods that can be used for App Services. Mutual authentication is only one of them. The main purpose is to enforce a client to provide a certificate over TLS/SSL to authenticate. The validation of this certificate takes place on the server side. Inside of an Azure Web App we get requests from a back end that authenticates itself by a client certificate by default. Therefore we had no other option but to implement it.
How it’s not done
Normally it is possible to implement Client Certificate Validation in Node.js using the https package. Therefore one could simply add the required Client Certificate by providing an options object like that:
1 2 3 4 5 6 7 8 |
const options: ServerOptions = { key: readFileSync('certs/server-key.pem'), cert: readFileSync('certs/server-crt.pem'), ca: readFileSync('certs/ca.pem'), requestCert: true, rejectUnauthorized: false }; const server = createServer(options, app); |
But unfortunately, in case of an Azure Web App this is NOT possible, because the service is not available directly from the internet. Basically a request from outside of Azure is being proxied through some components – like a load balancer for example – first. Only then it is received by the Node.js server. The Azure App Service or to be more precise the underlying IIS also terminates the SSL connections. After that it routes the request to the Node.js server without any encryption (http not https).
The Solution
As just shown, it is not an option to use the regular Node.js https approach in the service. Instead there is a way that is described on Azure docs, but as already mentioned, that is not giving details on a Node.js implementation.
As already described, Azure is terminating the SSL connection before the Node.js server gets the request. In order to forward the client certificate, it writes the certificate as base64 encoded string to the “X-ARR-ClientCert” header and adds it to the proxied request. So the Node.js / express server can validate the certificate and react according to the input. In the next sections it is shown how one can implement the validation in Node.js and how to configure Azure such that everything works.
The coding part
The key is to register a middleware that gets the “X-ARR-ClientCert” header and subsequently uses node-forge to convert the incoming base64 encoded PEM string into a certificate object. Which, in turn, can then be validated as shown in the example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import { NextFunction, Request, Response } from 'express'; import { pki, md, asn1 } from 'node-forge'; export class AuthorizationHandler { public static authorizeClientCertificate(req: Request, res: Response, next: NextFunction): void { try { // Get header const header = req.get('X-ARR-ClientCert'); if (!header) throw new Error('UNAUTHORIZED'); // Convert from PEM to pki.CERT const pem = `-----BEGIN CERTIFICATE-----${header}-----END CERTIFICATE-----`; const incomingCert: pki.Certificate = pki.certificateFromPem(pem); // Validate the fingerprint / thumbprint of the certificate (use your own certificates' thumbprint) const fingerPrint = md.sha1.create().update(asn1.toDer((pki as any).certificateToAsn1(incomingCert)).getBytes()).digest().toHex(); if (fingerPrint.toLowerCase() !== '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12') throw new Error('UNAUTHORIZED'); // Validate time validity const currentDate = new Date(); if (currentDate < incomingCert.validity.notBefore || currentDate > incomingCert.validity.notAfter) throw new Error('UNAUTHORIZED'); // Validate Issuer (use your own issuers' hash | alternative: compare it field by field like in the Azure documentation) if (incomingCert.issuer.hash.toLowerCase() !== '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12') throw new Error('UNAUTHORIZED'); // Validate Subject (use your own subjects' hash | alternative: compare it field by field like in the Azure documentation) if (incomingCert.subject.hash.toLowerCase() !== '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12') throw new Error('UNAUTHORIZED'); next(); } catch (e) { if (e instanceof Error && e.message === 'UNAUTHORIZED') { res.status(401).send(); } else { next(e); } } } } |
Please be aware: This is a SAMPLE verification routine. Depending on your application logic and security requirements, you should modify this method according to your needs. The code shown above, for example, does not contain a test if the certificate chains to a Trusted Root Authority, because in our use case the certificate is self signed. If you want to add this test as well, you could for example simply use the “verify” utility function that node-forge provides to you (documented here).
The configuration part
In Azure it is necessary to enable “HTTPS Only” in order to enforce SSL connections and enable “Client Certificates” to tell the IIS Server to add the “X-Arr-ClientCert” header. Otherwise the certificate will not be appended to the proxied request. This is done by changing it inside of the “SSL settings” of the App Service like shown in the picture below.
Conclusion
Now you should be able to get the client certificate from the request object and to validate the certificate according to your needs.
Comment article
Recent posts






Comments
Janis Köhr
Hi Mohit,
thank you, glad to hear that. I did not try to validate the certificate in MS IIS yet, but it might be possible. There are couple of reasons why we didn’t try in our project, though:
1. The standard way described in the azure docs were pointing out, that the IIS does not validate the certificate and will hand it over to the app (so we followed that path).
2. I personally think, manipulating configuration of the underlying IIS (even if it might be possible), is not a good practice, if it’s not a setting provided by the web app by default.
3. We have to provide some endpoints, that should not be secured. Therefore we validate the given certificate only when a certain endpoint gets called. Although with that solution the IIS still asks for a certificate. It is at least possible to give any certificate you want.
All in all I want to mention that, once you have mutual authentication (client certificates) enforced, there is a lot more to think of, like using KeyVault to store the Certificates etc. Maybe it is worth to look for alternative ways of authentication/authorization within your specific use case. It could also make sense to have a look on virtual networks in Azure. Maybe this already provides the isolation you mentioned in your comment.
Hope that helps 🙂
Janis
Mohit Jain
Great Post. This might be a naive question but is it possible to validate the certificate in MS IIS somehow. We are planning a mutual auth between API management and Azure web app and do not want our azure web app to be available from anywhere else but only through the API management and hence we are looking for easy ways to implement mutual AUth