T04 (mTLS) had grown a "WebSocketClient" subsection describing wss:// client certificates, and c12/t02 were getting similar WebSocketClient asides for timeouts and CA paths. The Cookbook's own index already separates WebSocket into its own category (W01-W04) from TLS/Security (T01-T05) and Client (C01-C19), so burying WebSocketClient specifics inside those pages fought the site's structure. Move that content into two new recipes under the WebSocket category instead: - W05: wss:// TLS setup (set_ca_cert_path CA directory parity, PemMemory client certificate) - W06: WebSocketClient's three timeouts, including the recently added chrono overloads T04, T02, C12, and W01 now carry a single reference link to the new pages instead of duplicated explanations, matching the site's existing cross-link convention. While rewriting T04's client-side section, noticed it documented SSLClient's file-path constructor but not its PemMemory one, even though the server-side section covered both forms for SSLServer. Added the missing PemMemory example so both sides are symmetric.
3.0 KiB
title, order, status
| title | order | status |
|---|---|---|
| T04. Configure mTLS | 45 | draft |
Regular TLS verifies the server certificate only. mTLS (mutual TLS) adds the other direction: the client presents a certificate too, and the server verifies it. It's common for zero-trust API-to-API traffic and internal system authentication.
Server side
Pass the CA used to verify client certificates as the third (and fourth) argument to SSLServer.
httplib::SSLServer svr(
"server-cert.pem", // server certificate
"server-key.pem", // server private key
"client-ca.pem", // CA that signs valid client certs
nullptr // CA directory (none)
);
svr.Get("/", [](const httplib::Request &req, httplib::Response &res) {
res.set_content("authenticated", "text/plain");
});
svr.listen("0.0.0.0", 443);
With this, any connection whose client certificate isn't signed by client-ca.pem is rejected at the handshake. By the time a handler runs, the client is already authenticated.
Configure with in-memory PEM
httplib::SSLServer::PemMemory pem{};
pem.cert_pem = server_cert.data();
pem.cert_pem_len = server_cert.size();
pem.key_pem = server_key.data();
pem.key_pem_len = server_key.size();
pem.client_ca_pem = client_ca.data();
pem.client_ca_pem_len = client_ca.size();
httplib::SSLServer svr(pem);
This is the clean way when you load certificates from environment variables or a secrets manager.
Client side
On the client side, pass the client certificate and key to SSLClient.
httplib::SSLClient cli("api.example.com", 443,
"client-cert.pem",
"client-key.pem");
auto res = cli.Get("/");
Note you're using SSLClient directly, not Client. If the private key has a password, pass it as the fifth argument.
The client side has the same PemMemory struct too, letting you set the client certificate from PEM in memory.
httplib::SSLClient::PemMemory pem{};
pem.cert_pem = client_cert.data();
pem.cert_pem_len = client_cert.size();
pem.key_pem = client_key.data();
pem.key_pem_len = client_key.size();
httplib::SSLClient cli("api.example.com", 443, pem);
auto res = cli.Get("/");
For mTLS with a WebSocket client (
wss://), see W05. Configure TLS for wss:// Connections.
Read client info from a handler
To see which client connected from inside a handler, use req.peer_cert(). Details in T05. Access the peer certificate on the server.
Use cases
- Microservice-to-microservice calls: Issue a cert per service, use the cert as identity
- IoT device management: Burn a cert into each device and use it to gate API access
- An alternative to internal VPN: Put cert-based auth in front of public endpoints so internal resources can be reached safely
Note: Issuing and revoking client certificates is more operational work than password-based auth. You'll need either an internal PKI setup or an automated flow using ACME-family tools.