56. Validating Client Certificates on the Server
56. Validating Client Certificates on the Server: An In-Depth Guide with Code Examples and Diagrams
Validating client certificates on the server is a fundamental element of building secure communication systems based on TLS/SSL. Many engineers are familiar with HTTPS and SSL certificates for servers—but quite a few have never implemented client authentication because it is perceived as complex or bureaucratically heavy. In reality, client certificate validation provides a significant additional layer of protection, especially for internal systems, APIs, or service-to-service communication (mTLS). In this article we will thoroughly explore client certificate validation, from the concept and the flow to implementation practices (code examples) and a hands-on simulation, so you can start applying it directly in your own projects.
What Is Client Certificate Validation?
In general, when establishing a TLS connection, the server presents a certificate to the client to prove its identity. However, with mutual TLS (mTLS), the client must also present a certificate to the server. The server then validates the client certificate before granting access.
Validation typically covers:
- A trusted issuer (CA)
- The certificate validity period (valid from & valid until)
- Certificate features/fingerprint (for example, a particular extension)
- Revocation status (CRL/OCSP)
The direct benefits are:
- Two-way authentication: Not only is the server verified, but the client is verified as well.
- Rejecting unauthorized clients: Only clients with a valid certificate can access the server/API.
- A better fit for machine-to-machine communication: For example, between microservices, IoT gateways, and so on.
Flow Diagram: TLS Handshake with Client Certificate Validation
Before diving into the code, let’s look at the handshake flow using the following mermaid diagram:
sequenceDiagram
participant Client
participant Server
Client->>Server: Client Hello (cipher suite, etc)
Server->>Client: Server Hello + Certificate
Server->>Client: (Optional) Certificate Request
Client->>Server: Client Certificate
Client->>Server: Key Exchange
Server->>Client: Server Finished
Client->>Server: Client Finished
Note over Server: Validasi Sertifikat Client
(CA trust, Validity, Revocation)
During the Certificate Request stage, the server asks the client for its certificate. Once received, the server validates it before continuing the handshake.
Applying It in the Real World: Simulation and Code
Let’s run a simple simulation with a web server built on Node.js using the Express framework. Our focus is on:
- Preparing the root CA, the server certificate, and the client certificate
- Setting up an Express server that validates the client certificate
- Simulating client requests using
curl
1. Preparing the Certificates (with OpenSSL)
a. Create the root CA
1openssl genrsa -out rootCA.key 2048
2openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem -subj "/CN=RootCA"b. Create the server certificate
1openssl genrsa -out server.key 2048
2openssl req -new -key server.key -out server.csr -subj "/CN=localhost"
3openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500 -sha256c. Create the client certificate
1openssl genrsa -out client.key 2048
2openssl req -new -key client.key -out client.csr -subj "/CN=client"
3openssl x509 -req -in client.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out client.crt -days 500 -sha256In the end, we’ll have the files: rootCA.pem, server.crt, server.key, client.crt, client.key.
2. Configuring Express with Client Certificate Validation
The simple code below forces the client to send a certificate, and the server will reject any request without a certificate or with an invalid one.
1const fs = require('fs');
2const https = require('https');
3const express = require('express');
4
5const app = express();
6
7// TLS configuration with the CA and client cert validation
8const options = {
9 key: fs.readFileSync('server.key'),
10 cert: fs.readFileSync('server.crt'),
11 ca: fs.readFileSync('rootCA.pem'),
12 requestCert: true, // Require the client to send a certificate
13 rejectUnauthorized: true, // Reject if invalid/CA is unknown
14};
15
16app.get('/', (req, res) => {
17 // req.client.authorized is automatically true if the certificate is valid & trusted
18 if (req.client.authorized) {
19 res.send('Hello, Client with a valid certificate!');
20 } else {
21 res.status(401).send('Access denied: invalid client certificate');
22 }
23});
24
25https.createServer(options, app).listen(3443, () => {
26 console.log('Server listening at https://localhost:3443');
27});The Express server above will only accept requests from clients with a valid certificate signed by the same Root CA.
3. Simulating Client Requests
- Client request with a valid certificate:
1curl -v https://localhost:3443 \
2 --cacert rootCA.pem \
3 --cert client.crt \
4 --key client.keyHello, Client with a valid certificate!- Client request WITHOUT a certificate:
1curl -v https://localhost:3443 --cacert rootCA.pemAccess denied: invalid client certificateCase Study: Client Authorization Scheme Table
Suppose you manage an internal payroll API service. The clients allowed in are a specific list that has each been issued its own certificate. Each certificate is given a Common Name (CN) matching the name of the application/institution:
| CN | Application | Expired | Accepted by Server? |
|---|---|---|---|
| payroll-app | Payroll System | 2025-01-01 | ✔️ Yes |
| finance-app | Finance Dashboard | 2023-12-01 | ❌ No |
| ext-user | External Vendor | 2025-05-20 | ✔️ Yes |
| unknown-user | Not registered | 2025-06-30 | ❌ No |
Further validation can be performed through the SAN (Subject Alternative Name) field or other extensions, for example to manage roles/privileges.
Best Practices for Client Certificate Validation
Use a trusted CA and manage its distribution securely.
Avoid self-signed CAs for large systems.Automate certificate rotation & revocation.
Implement a system to renew/deactivate client certificates that have expired or been revoked.Maintain structured logging of all handshake activity.
Including who failed and why.Verify certificate extensions and attributes according to your organization’s policies.
Use mutual TLS only for critical/internal endpoints,
because implementing it on the client side adds complexity.
Conclusion
Validating client certificates on the server is a strong security mechanism, especially in machine-to-machine communication scenarios that require two-way authentication. The implementation does require a bit of extra effort (a CA certificate, distributing client certificates, and so on), but the benefits are well worth the level of security you gain.
In the real world, practices such as mutual TLS have already become a best practice across many financial APIs, Open Banking, and service meshes (Istio/Linkerd). Hopefully the article and code examples above help you understand and start applying client certificate validation in your own systems.
Keep up the enthusiasm for tinkering with security features, because defense in depth will save you from a cybersecurity nightmare!
Further references