Mutual TLS Pattern
Server proves identity to client, but client proves identity to server too. No more credentials in a header — identity is carried by the certificate.
Problem
"Classic" TLS authenticates the server via its certificate (client verifies chain against a trusted CA). The client then authenticates via password, JWT, API key — all stealable secrets. How to bind the client's identity to the transport, without shared secret and without a stealable header?
Forces
- Passwords and API keys leak (logs, bug, phishing).
- JWT in header is vulnerable to replay if the network isn't encrypted.
- A full PKI has operational cost (CA, CRL/OCSP, rotation).
- Certificates expire and block flows if unrotated.
- The partner must also run a PKI — onboarding friction.
Solution
Extend the TLS handshake with an extra CertificateRequest from server to client. The client presents its own X.509 certificate; the server verifies it against a specific CA (the "partner CA" for example). The client's identity (Subject DN, SAN) is now carried by the certificate — usable directly as application identity. For rotation, support validity overlap; for revocation, consult CRL or OCSP stapling. Modern Service Meshes (Istio, Linkerd) automate mTLS between microservices via SPIFFE / SPIRE.
mTLS handshake
Client Server
│ │
│ ClientHello (TLS 1.3, cipher suites) │
│ ─────────────────────────────────────────► │
│ │
│ ServerHello + Server Certificate │
│ + CertificateRequest │
│ ◄───────────────────────────────────────── │
│ │
│ Client Certificate (X.509) │
│ + CertificateVerify (sig with priv key) │
│ + Finished │
│ ─────────────────────────────────────────► │
│ │
│ Server verifies chain │
│ vs trusted CA │
│ ──► identity = Subject DN │
│ │
│ Finished │
│ ◄───────────────────────────────────────── │
│ │
│ Encrypted application data │
│ ◄──────────────────────────────────────── ►│ EDI implementation
Every modern EDI protocol relies on mTLS: AS2 mandates S/MIME signing but the HTTPS envelope is often mTLS when the partner requires it; AS4 via OASIS ebMS3 uses mTLS for transport. PEPPOL mandates mTLS between Access Points. An internal Service-Mesh-based hub (Istio) encrypts all east-west traffic with automatic mTLS — no API key needed between microservices. For partner onboarding, exchange CSRs (Certificate Signing Requests) out-of-band and sign with the hub's dedicated CA.
Anti-patterns
- Disabling chain validation for speed ("—
insecure_skip_verify") — equivalent to no TLS. - Reusing one CA for all uses (internal + partners) — catastrophic compromise.
- Self-signed unmanaged cert — silent expiry.
- No CRL or OCSP — a compromised cert stays valid until expiry.
- Rotation every 2 years without a drill — rotation breaks production on D-day.
Related patterns
- Zero Trust Architecture — mTLS is the cryptographic brick.
- Secrets Management — manages private keys and certificates.
- Service Mesh — automates mTLS on infrastructure.
- Defense in Depth — mTLS is an identity layer.
Sources
- RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3. rfc-editor.org/rfc/rfc8446
- RFC 5246 — The Transport Layer Security (TLS) Protocol Version 1.2.
- SPIFFE — Secure Production Identity Framework for Everyone. spiffe.io/docs/latest/spiffe-about/overview/
- OpenPEPPOL — eDelivery Network Specifications (mTLS profiles for AS4 between Access Points).