mTLS and Mutual Authentication

On this page

mTLS authenticates both parties in a TLS connection. In common certificate modes, the client verifies the server, and the server also requests and verifies the client certificate along with proof of private key possession. It associates the connection with a verified principal but does not automatically determine which tenant the principal can read or which API it can call.

Consider an order service calling a payment service: mTLS can confirm that the caller is a trusted order workload; whether it is allowed to execute a charge or refund is still determined by the payment service's authorization policies. A test service from the same CA should not thereby gain production payment permissions.

Common QuestionCore Distinction
Does requesting a client certificate mean a certificate is mandatory?The request mechanism and the policy rejecting anonymity are separate.
Must I set up my own CA?You can use controlled managed issuance or pre-registered trust methods.
Does passing certificate verification mean access is granted?Identity mapping and specific permission checks are still required.
Does enabling mTLS on the gateway propagate to the backend?It depends on the actual TLS termination point.
Is the SPIFFE ID written in the CN?X.509-SVID uses the URI SAN.
Does rotating certificates immediately replace all connection identities?Existing connections and session resumption have independent states.

Handshake: Certificates, Signatures, and Key Confirmation Complement Each Other

Client Certificate Authentication in TLS 1.3 Initial Handshake Client Server ClientHello + key_share ServerHello; then encrypted transmission of EncryptedExtensions, CertificateRequest Encrypted: Server Certificate, CertificateVerify, Finished Encrypted: Client Certificate, CertificateVerify, Finished After both parties verify identities, the application still needs to authorize based on principal / action / resource. Diagram shows the complete handshake when the client has a suitable certificate; responses and server policies for missing certificates must be handled separately.

In the full certificate authentication handshake of TLS 1.3, the server sends CertificateRequest, and the client sends Certificate, CertificateVerify, and Finished if it has a suitable certificate. The certificate expresses the identity recognized by the issuer, CertificateVerify binds the proof of private key possession to this handshake, and Finished confirms the handshake keys and history.

When the client cannot find a suitable certificate, it returns an empty certificate_list and does not send CertificateVerify; the server can continue anonymously based on policy or abort with certificate_required. Gateways requiring mTLS must ensure that anonymous paths are rejected; it is not enough to just check if CertificateRequest was sent.

certificate_authorities is a hint for certificate selection; omitting it does not mean trusting any CA. When this extension appears in TLS 1.3, the list itself has a non-empty length requirement; the absence of the extension is not the same as encoding an empty list. signature_algorithms, oid_filters, etc., also have their own definitions; there is no universal process for "filling in the SPIFFE OID in certificate_extensions". RFC 9846: Client Authentication

TLS 1.3 also supports post-handshake authentication after negotiation; its implementation and upper-layer protocol support need to be confirmed, and one cannot assume that temporary authentication can be added when accessing a specific HTTP path. In practice, it should first be clarified whether the gateway requires client identity for the entire connection, and then design routing and authorization.

How to Obtain an Authorizable Identity After Verification

Chain verification is responsible for confirming conditions such as issuer relationships, time, and usage; identity mapping is responsible for obtaining the principal according to agreed-upon fields. Server identities commonly use DNS SAN, while clients may use URI SAN, specially registered certificate mappings, or other application conventions. Do not arbitrarily take the first CN string as a system-wide unique identity.

Take spiffe://prod.example/ns/orders/sa/api as an example; it can be used as an explicitly allowed principal, and then it is decided whether it can execute charge. The same path appearing under spiffe://staging.example/... is a different identity; containing orders in the string or having prod.example as a prefix is not enough.

Authentication and authorization failures should be recorded separately: unknown issuer, inappropriate certificate usage, and unmappable identity belong to different problems; an authenticated principal lacking refund permissions is an authorization rejection. This allows explaining "TLS success but HTTP 403", rather than uniformly re-signing certificates.

Teaching Strategy: Default Deny, Authorize Actions Individually

The input to the following function must be generated by a real verification layer; it does not read client-reported HTTP headers, nor does it implement X.509 or full SPIFFE parsing. It only demonstrates how authentication results participate in subsequent policies.

from dataclasses import dataclass

@dataclass(frozen=True)
class Peer:
    authenticated: bool
    identity: str | None

RULES = {
    'spiffe://prod.example/ns/orders/sa/api': {'charge'},
    'spiffe://prod.example/ns/finance/sa/worker': {'refund'},
}

def allow(peer, action, tenant, permitted_tenants):
    return (peer.authenticated
            and peer.identity in RULES
            and action in RULES[peer.identity]
            and tenant in permitted_tenants.get(peer.identity, set()))

orders = 'spiffe://prod.example/ns/orders/sa/api'
tenants = {orders: {'tenant-a'}}
assert allow(Peer(True, orders), 'charge', 'tenant-a', tenants)
assert not allow(Peer(True, orders), 'refund', 'tenant-a', tenants)
assert not allow(Peer(True, orders), 'charge', 'tenant-b', tenants)
assert not allow(Peer(False, orders), 'charge', 'tenant-a', tenants)
assert not allow(Peer(True, orders.replace('prod.', 'staging.')), 'charge', 'tenant-a', tenants)
assert not allow(Peer(True, orders + '/extra'), 'charge', 'tenant-a', tenants)
assert not allow(Peer(True, None), 'charge', 'tenant-a', tenants)
print('Principal, action, tenant, and unauthenticated rejection branches passed')

Workload identities and user identities should also be separated. When the order service initiates operations on behalf of a user, mTLS proves the order service; the user, tenant, and delegation scope require reliable request context or tokens, and it must be checked whether the service has the right to perform the action on behalf of that user.

Where Does Trust Come From: Private CAs Are Common, But Not the Only Way

Building or hosting a private CA facilitates control over service identity naming, issuance conditions, and trust scope. The client authentication trust store should limit usage and cannot directly accept any valid client certificate under the entire public browser root library for convenience.

mTLS does not require the organization to personally operate an offline root CA. For example, OAuth's mTLS client authentication defines both PKI methods and self_signed_tls_client_auth based on registration materials. The latter trusts pre-bound client public key materials, not "accepting any self-signed certificate seen". RFC 8705: Client Authentication

The issuance system also requires initial identity basis. A new instance cannot simply submit "I am payments" to obtain payment identity; it needs platform evidence, workload registration, and policies to establish authorization relationships. Certificate issuance automation solves credential delivery but does not solve the initial trusted identity problem out of thin air.

SPIFFE: Identity Specification, Credentials, and Delivery Interfaces

NameResponsibility
SPIFFE IDIdentifies workloads under the trust domain
X.509-SVIDExpresses verifiable SPIFFE identity using X.509
JWT-SVIDExpresses another SVID using JWT, not equivalent to mTLS certificate
Trust bundlePublic trust material for verifying credentials of the corresponding domain
Workload APIDelivers credentials, keys, and trust updates to identified workloads
SPIREOne implementation of SPIFFE, responsible for attestation, registration, and credential management

X.509-SVID must have exactly one URI SAN to carry the SPIFFE ID; it can have other types of SANs, but cannot fall back to CN, nor can it randomly select one among multiple URIs. Leaf certificates are also subject to CA flags, KU, and identity path constraints; verifying SPIFFE identity requires using the trust material of the corresponding trust domain. X.509-SVID Specification

SVID is not "the certificate content contains the private key": the certificate is a public object, and the Workload API can deliver the corresponding private key as another material. Short-term rotation is part of the overall design, but "default one hour" cannot be written as a permanent unified SPIFFE regulation. Multiple instances can also share service-level identities, which does not require them to share the same private key. SPIFFE Concepts

Proxy Boundaries: Certificate Identities Do Not Automatically Cross TLS Termination

Proxy Terminated mTLS: The Backend First Authenticates the Proxy Client A Holds A's certificate Gateway G Verifies A, executes ingress policies Backend B TLS peer is G mTLS ① mTLS ② A's identity is passed via trusted request context Clean up external same-name headers; backend restricts sources and verifies gateway identity Two connections are authenticated separately; forwarding certificate text does not mean A proved private key possession in the second handshake.

If the gateway terminates A's mTLS and then establishes a second mTLS to B, the TLS peer directly authenticated by B is the gateway G. If B needs the identity of the original caller A, a trusted delivery convention from the gateway to the backend should be defined, and the backend should be limited to accepting this convention only from qualified gateways.

Take X-Forwarded-Client-Cert as an example; it is a configurable proxy header: the ingress must clean up or overwrite externally forged values, then generate content based on the verified connection; the backend needs to verify the source, delivery rules, and required fields. Putting a PEM or Subject text into a header does not allow A to participate in the CertificateVerify of the second TLS. Envoy: XFCC

If TLS passthrough is used, the backend completes the original handshake directly, but the gateway usually cannot simultaneously read the encrypted HTTP path for regular layer 7 routing. Choosing between termination and passthrough depends on which layer needs to see the identity and business information.

Does the Backend Authenticate the Original Caller or the Gateway?

In termination mode, even if the original principal is trustworthy, B's direct TLS peer is still the gateway. If any delivery condition is broken, the orders can no longer be treated as a trusted original principal. In passthrough mode, A and B handshake directly; changing the action to refund will still result in an authorization rejection.

Preparing the visual
Does the Backend Authenticate the Original Caller or the Gateway?

Two mTLS connections establish two connection identities. Trusted proxy delivery can carry the original principal, but it does not cause the original caller to participate in the latter half of the handshake; authorization also requires matching principal and action.

Service Mesh: Separate Encryption Coverage from Permission Policies

The Mesh can have proxies handle certificate delivery and mutual authentication, but the actual coverage depends on whether traffic enters the data plane, whether workloads are managed, and port policies. Sidecar is just one deployment method; not all meshes can be drawn as having a sidecar in every Pod.

For example, in Istio, PeerAuthentication controls the receiving side's mTLS requirements, and AuthorizationPolicy controls access authorization. PERMISSIVE can be compatible with plaintext and mTLS; one cannot infer that all ingress points have rejected plaintext just by "enabling automatic mTLS"; ambient and other modes also have their own capability constraints. Istio Security Concepts, PeerAuthentication

Encryption between proxies does not mean that applications between local proxies necessarily use TLS. Evaluation should draw real termination points, process isolation, and bypassable ingress points, rather than just judging end-to-end identity boundaries based on a green lock displayed on the console.

Rotation: Old Certificates, Old Connections, and Resumption Tickets Are Three States

Assume the leaf certificate in the teaching deployment is valid for two hours, rotating every hour. A new certificate issued at 10:00 being replaced at 11:00 does not mean that a long connection established at 10:30 immediately re-authenticates. Certificate expiration usually does not automatically trigger chain verification on every application write; whether to limit connection lifetime, actively disconnect, or re-authorize requires explicit policies.

Root or intermediate CA rotation can use overlapping trust: first distribute a bundle containing old and new anchors, confirm that the receiver has loaded it, then switch to the new issuance chain, and finally remove the old trust after the old credentials expire and other related states are handled. Switching only the issuer first will cause clients that have not yet updated to immediately lose interoperability.

Session resumption may inherit the previous authentication context without resending certificates. When disabling an identity, in addition to new handshake verification, existing connections, resumption tickets, and authorization caches must be considered; short validity periods only shorten part of the exposure window. One cannot treat 0-RTT as an ordinary request that has completed this client certificate authentication just because the system uses mTLS.

How to Collaborate with API Keys and Access Tokens

MechanismCommon Proof ObjectParts That Should Be Designed Separately
mTLSTLS peer holds private key bound to identityTermination point, trust store, connection and credential lifecycle
API KeyRequester holds an application credentialTransport protection, permissions, revocation, and leak handling
Bearer access tokenRequester holds an issued access tokenissuer/audience/expiration/permissions and replay boundaries
Certificate-bound access tokenToken is associated with the current client certificateResource server must check the binding relationship

OAuth certificate-bound tokens associate the certificate digest with the token; the resource server checks whether the current mTLS certificate matches the binding, reducing availability after the token is leaked alone. It is an example of collaboration between transport identity and request authorization, and cannot replace verification with "using JWT means no need to look at mTLS". RFC 8705: Binding Access Tokens

There is no fixed overhead ranking detached from connection reuse, signature algorithms, token verification caches, and request scale. API Keys cannot be labeled as inherently incompatible with zero trust either: the key is to clarify identity, least privilege, and continuous enforcement policies.

Continue Reading