Mutual TLS and operations
Require client authentication
Configure the local server to request and verify certificates from the lab CA.
10 minute lesson
In mutual TLS, the client verifies the server and the server also verifies the client certificate chain. The server needs three pieces of configuration for that: ask for a certificate, refuse connections that fail verification, and know which CA to verify against.
In our Node lab server, that is three new options. Add client verification to the Node HTTPS options:
https.createServer({
key: fs.readFileSync('app.key'),
cert: fs.readFileSync('app.crt'),
requestCert: true,
rejectUnauthorized: true,
ca: fs.readFileSync('lab-ca.crt'),
}, (request, response) => {
response.end(`hello ${request.socket.getPeerCertificate().subject.CN}\n`)
}).listen(8443, '127.0.0.1')
requestCert: true makes the server ask every client for a certificate during the handshake. rejectUnauthorized: true drops clients whose certificate does not verify. ca sets the trust anchor for that verification — only certificates chaining to our lab CA pass. The handler shows the payoff: getPeerCertificate() exposes the verified client identity, so the application can read CN = lab-client from the socket.
Now test both sides of the door. Connect once without a client certificate:
curl --cacert lab-ca.crt --resolve app.lab.test:8443:127.0.0.1 https://app.lab.test:8443/
# curl: (56) ... alert certificate required
The handshake fails — not with an HTTP 401, but at the TLS layer, with an alert telling you a certificate was required. Unauthenticated clients never reach the application code at all.
Then connect with the client credentials from the previous lesson, using curl --cert client.crt --key client.key:
curl --cacert lab-ca.crt --cert client.crt --key client.key --resolve app.lab.test:8443:127.0.0.1 https://app.lab.test:8443/
# hello lab-client
Record the different handshake results. Same server, same URL: one connection rejected during the handshake, one greeted by name.
A failure worth recognizing: everything configured, but the client certificate was signed by a different CA than the one in the server’s ca option. The symptom is the same handshake alert as having no certificate. When mTLS “doesn’t work”, verify the client certificate against the server’s expected CA with openssl verify -CAfile lab-ca.crt client.crt before touching code.
One boundary to keep sharp: authentication is not authorization. Do not treat a valid client certificate as unlimited authorization. The handshake proved who is connecting; the application still decides what that identity may do. Map the verified identity to narrow application permissions, exactly as you would with any logged-in user.
Lesson completed