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.
This guide assumes you've completed the installation guide.
You should already have:
@monocloud/backend-node SDK installed.envCreate a Fastify server with mTLS and certificate binding validation:
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:
requestCert: true tells the server to request a client certificate during the TLS handshakerejectUnauthorized: false allows the application to handle certificate validation instead of rejecting the request at the TLS levelcertificateResolver extracts the PEM-encoded client certificate from the TLS connectionvalidateCertificateBinding: true verifies that the token's cnf.x5t#S256 claim matches the SHA-256 thumbprint of the client certificate| Scenario | Status code | Response |
|---|---|---|
| Missing or invalid token | 401 | { "message": "unauthorized" } |
| Token not bound to a certificate | 401 | { "message": "unauthorized" } |
| Certificate thumbprint mismatch | 401 | { "message": "unauthorized" } |
| Valid token with matching certificate | — | Route handler executes normally |