Configure an Express Application with mTLS and express-ws

Article summary

I recently spent some time adding mutual TLS (mTLS) support to a Node.js Express app. The app was using a library called express-ws to handle WebSocket connections.

Problem

There are hundreds of examples out there of how to set up an Express app with a certificate and key for TLS. And there are dozens of other examples of how to set up a TLS-enabled WebSocket server, using a variety of different libraries. But it took several iterations before I got things just right for the specific combination of Express/express-ws/mTLS.

I did eventually figure it out. And, of course, after getting it working, I found an example in the express-ws repository that lays out exactly what I needed to do.

But if anyone else finds themselves working with this same combination of technologies, I hope this post can save them time and searching.

Solution

Here’s what I ended up with:


const fs = require("fs");
const https = require("https");
const express = require('express');
const expressWs = require('express-ws');

const options = {
  key: fs.readFileSync(`${__dirname}/certs/private.key`),
  cert: fs.readFileSync(`${__dirname}/certs/server-cert.pem`),
  ca: [
    fs.readFileSync(`${__dirname}/certs/ca-root-cert.pem`),
  ],
  requestCert: true,
  rejectUnauthorized: true,
};

const app = express();
const server = https.createServer(options, app);

// Apply the express-ws support, passing the https server as well
expressWs(app, server);

app.get('/', (req, res) => {
  res.send(JSON.stringify({hello: "world"}));
  res.end();
});

app.ws('/', (ws) => {
  console.log("Got websocket connection")

  ws.on("message", async (message) => {
    const data = JSON.parse(message);
    console.log("Received message", data)
    ws.send(JSON.stringify({ body: "foo" }));
  });
});

server.listen(3000)
console.log("Express HTTP and Websocket server listening on 3000");

To summarize the code, the right order of operations ended up being:

  1. Create the Express app.
  2. Create the HTTPS server, providing the TLS options and the Express app.
  3. Pass both of those things to the expressWs function.
  4. Add the route handlers.
  5. Call listen on the HTTPS server object.

The express-ws library makes it easy to add WebSocket support seamlessly to an Express application. It’s often a little tricky to get things set up for HTTPS and WSS, so I hope this was helpful!