Use SSL Client Authentication with node.js and Express

For a recent engagement, I was asked to set up client authentication for a node.js/express app. With this type of authentication, an SSL certificate is required to access the site.

To get the most out this article, download and run the accompanying this demonstration app. It contains scripts to create the necessary certificates, similar to what you see here. These scripts and this article use openssl, which can be found in Linux or the Git for Windows bash shell.

For these purposes you don’t need to understand everything about certs, but here are some basics:

Create a signing key (CA)

Everything starts with a signing key, which can be created with a command similar to this:

openssl req -new -x509 -days 365 -keyout ca-key.pem -out ca-crt.pem

The resulting cert and key can then be used to sign other certificates. Creating a self signed cert is similar.

Create a signed certificate

Creating a signed certificate is a multi-step process. First you’ll need a signing key, as generated above.

export CERT=server
export CA_NAME=ca

#generating private key for server
openssl genrsa -out $CERT-key.pem 4096

#generate a signing request
openssl req -new -sha256 -config $CERT.cnf -key $CERT-key.pem -out $CERT-csr.pem

#perform the signing
openssl x509 -req -days 365 -in $CERT-csr.pem -CA $CA_NAME-crt.pem -CAkey $CA_NAME-key.pem -CAcreateserial -out $CERT-crt.pem

The initial command (genrsa) generates public and private keys. The third command’s final product, server-crt.pem, is used along with the private key to run ssh in express.

Set up https

To start, your express app must be running https, so you’ll need to have (or generate) a public and private key for that. Here is an example of how to set that up:

var express = require('express');
var fs = require('fs');
var https = require('https');
var app = express();
var options = {
    key: fs.readFileSync('certs/server-key.pem'),
    cert: fs.readFileSync('certs/server-crt.pem'),
};
app.use(function (req, res, next) {
    res.writeHead(200);
    res.end("hello world\n");
    next();
});
var listener = https.createServer(options, app).listen(4433, function () {
    console.log('Express HTTPS server listening on port ' + listener.address().port);
});

Test https

$ node app
Express HTTPS server listening on port 4433

Test using curl in another window. Note that we use -k to bypass certificate validation.

$ curl https://127.0.0.1:4433 -k
hello world

Set up client authentication

Once you have ssh cooperating, you can add client auth. requestCert will require the client (e.g. curl, browser) to send a cert. rejectUnauthorized=false allows us to handle validation ourselves.

If you’re using a browser,

You’ll notice that now we have the client auth certificate. If this was created as a CA, then ANY certificate which is signed by this one will validate.

var options = {
    key: fs.readFileSync('certs/server-key.pem'),
    cert: fs.readFileSync('certs/server-crt.pem'),
    ca: fs.readFileSync('certs/ca.pem'), #client auth ca OR cert
    requestCert: true,                   #new
    rejectUnauthorized: false            #new
};

Here’s how we handle the validation:

app.use(function (req, res, next) {
   if (!req.client.authorized) {
       return res.status(401).send('User is not authorized');
   }
   #examine the cert itself, and even validate based on that!
   var cert = req.socket.getPeerCertificate();
   if (cert.subject) {
       console.log(cert.subject.CN);
   }
   next();
});

Test client authentication

# Access the site using the client certificate created above.
curl -v -s -k --key certs/client1-key.pem --cert certs/client1-crt.pem https://localhost:4433

Test client authentication with a browser

  1. Add the client pfx file to your certificate store. If you’re using a newly created CA, you might need to add its pfx as well. If you’re not presented with a dialog box in step 3, this is likely the problem.
  2. Browse to https://localhost:4433.
  3. You should be presented with a dialog box. Choose the client certificate.
  4. Note that if you change the certificate, you will need to completely close Chrome (and possibly need to kill the task) in order to see the dialog again.

Certificates CAN be revoked. This is not covered in this article, but see the appropriate section here.

Other articles you might like:
HTTPS Authorized Certs with Node.js
How To Create an SSH CA to Validate Hosts and Clients with Ubuntu

Leave a Reply

Your email address will not be published. Required fields are marked *