Sign in

Validate mTLS Bound Tokens

This guide shows how to validate mTLS certificate-bound access tokens in a Fastify 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:

  • A Fastify 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 a Fastify server with mTLS and certificate binding validation:

src/server.ts
import "dotenv/config";
import Fastify, { type FastifyRequest } from "fastify";
import fs from "fs";
import { TLSSocket } from "tls";
import { X509Certificate } from "crypto";
import {
  protectApi,
  type ClientCertificateResolver,
  type AuthenticatedFastifyRequest,
} from "@monocloud/backend-node/fastify";

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

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

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

  return "";
};

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

// Validate certificate binding on all routes
app.addHook("onRequest", protect({ validateCertificateBinding: true }));

app.get("/api/data", async (request) => {
  const { claims } = request as AuthenticatedFastifyRequest;
  return { claims };
});

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

How it works:

  • Fastify's built-in HTTPS support handles the TLS configuration directly in the constructor
  • 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.