Sign in

Validate mTLS Bound Tokens

This guide shows how to validate mTLS certificate-bound access tokens in an Express API using the MonoCloud Backend Node SDK.

mTLS certificate binding ensures that an access token can only be used by the client that holds the corresponding TLS certificate.

What you'll cover

  • Configure an HTTPS server with mTLS
  • Implement a certificate resolver
  • Validate certificate binding globally

Before you begin

This guide assumes you've completed the installation guide.

You should already have:

  • An Express project
  • The @monocloud/backend-node SDK installed
  • Environment variables configured in .env
  • A server certificate and key for your HTTPS server

Configure the HTTPS server

Create an HTTPS server with mTLS and certificate binding validation:

src/server.ts
import "dotenv/config";
import express, { type Request } from "express";
import https from "https";
import fs from "fs";
import { TLSSocket } from "tls";
import { X509Certificate } from "crypto";
import {
  protectApi,
  type ClientCertificateResolver,
  type AuthenticatedExpressRequest,
} from "@monocloud/backend-node/express";

const app = express();
app.use(express.json());

// Resolve the client certificate from the TLS connection
const certificateResolver: ClientCertificateResolver<Request> = async (req) => {
  const { socket } = req;

  if (socket instanceof TLSSocket) {
    const cert = socket.getPeerCertificate(true);
    if (cert && cert.raw) {
      return new X509Certificate(cert.raw).toString();
    }
  }

  return "";
};

// Create the middleware with the certificate resolver
const protect = protectApi({ certificateResolver });

// Validate certificate binding on all routes
app.use(protect({ validateCertificateBinding: true }));

app.get("/api/data", (req, res) => {
  const { claims } = req as AuthenticatedExpressRequest;
  res.json({ claims });
});

// Create the HTTPS server
const server = https.createServer(
  {
    key: fs.readFileSync("<your-server-key.pem>"),
    cert: fs.readFileSync("<your-server-cert.pem>"),
    requestCert: true,
    rejectUnauthorized: false,
  },
  app
);

server.listen(3000, () => {
  console.log("HTTPS server running on https://localhost:3000");
});

How it works:

  • requestCert: true tells the server to request a client certificate during the TLS handshake
  • rejectUnauthorized: false allows the application to handle certificate validation instead of rejecting the request at the TLS level
  • The certificateResolver extracts the PEM-encoded client certificate from the TLS connection
  • validateCertificateBinding: true verifies that the token's cnf.x5t#S256 claim matches the SHA-256 thumbprint of the client certificate

Response behavior

ScenarioStatus codeResponse
Missing or invalid token401{ "message": "unauthorized" }
Token not bound to a certificate401{ "message": "unauthorized" }
Certificate thumbprint mismatch401{ "message": "unauthorized" }
Valid token with matching certificateRoute handler executes normally
© 2024 MonoCloud. All rights reserved.