How do services authenticate to each other in a microservices mesh — mTLS vs. service accounts/tokens?
Two services calling each other over the network need to prove who they are without a human in the loop — mTLS and token-based identity solve that in very different ways.
LEARN BY DOINGPut your intuition to work3 steps
How do services authenticate to each other in a microservices mesh — mTLS vs. service accounts/tokens?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#Why service-to-service identity matters
In a monolith, "who is calling this function" is answered by the call stack. In a microservices mesh, every call crosses a network boundary, and the network boundary is exactly where an attacker — or a misconfigured neighbor service — can show up pretending to be someone else. Once you have dozens or hundreds of services talking over HTTP or gRPC, "the request came from inside our VPC" stops being a security boundary. Zero-trust networking treats every hop, internal or external, as untrusted until proven otherwise, and proving otherwise is what service-to-service authentication is for.
There are two dominant answers to "how does Service B know it's really Service A calling?": mutual TLS (mTLS) at the transport layer, and service accounts/tokens (JWTs, OAuth2 client-credentials) at the application layer. They are not mutually exclusive — most mature platforms run both, layered — but interviewers want to see that you understand what each one actually proves.
#mTLS: proving identity at the transport layer
In ordinary TLS, only the server presents a certificate; the client verifies it and trusts the server, but the server has no cryptographic proof of who the client is. Mutual TLS flips this around: both sides present X.509 certificates during the handshake, and both sides verify the other's certificate against a trusted certificate authority (CA) before a single byte of application data moves.
In practice almost nobody hand-rolls this per service. A service mesh sidecar (Envoy in Istio/Linkerd-style architectures) intercepts outbound and inbound traffic, terminates TLS, and handles certificate issuance and rotation transparently — your application code just makes a plain HTTP call to localhost, and the sidecar wraps it in mTLS before it leaves the pod. Certificates typically encode a workload identity (a SPIFFE ID like spiffe://cluster.local/ns/payments/sa/checkout) rather than a human identity, are short-lived (minutes to hours), and are rotated automatically by something like SPIRE or the mesh's own CA — nobody wants to be manually rotating 400 service certificates.
#Service accounts and tokens: proving identity at the application layer
The alternative (or complement) is a bearer credential the caller attaches to the request itself — most commonly a signed JWT obtained via OAuth2's client-credentials grant, or a cloud-provider service account token (an AWS IAM role, a GCP service account, a Kubernetes ServiceAccount token). The calling service authenticates once against an identity provider or token issuer, gets back a short-lived token, and presents it as an Authorization: Bearer ... header on every call. The receiving service validates the token's signature and claims (issuer, audience, expiry, scopes) without needing a live round-trip to the issuer, because JWTs are self-contained and verifiable offline against the issuer's public key.
#The real tradeoff
mTLS authenticates the connection — it tells you which workload opened this TCP connection, and it does so for every packet, transparently, without any application code changes. It's excellent for defense-in-depth and for encrypting east-west traffic, but it says nothing about why the call is being made or on whose behalf, and revoking a compromised identity means revoking a certificate (which some CAs make slow via CRLs, though short-lived certs sidestep this by simply not renewing them).
Tokens authenticate the request, and they can carry rich claims — scopes, tenant IDs, the original end-user's identity if the token is a delegated/on-behalf-of token — so the receiving service can make fine-grained authorization decisions ("this token has orders:read but not orders:write"). But tokens require application-level integration (middleware to validate them), and a leaked long-lived token is a real risk if expiry and scope are not tightly controlled.
#The handshake, with a sidecar in the loop
Notice that Service A and Service B never see TLS at all — they speak plain HTTP to their own sidecar over the loopback interface, and the sidecars handle the cryptographic handshake, certificate verification, and encryption. This is precisely what lets platform teams roll out mTLS across an entire fleet without touching application code.
#Picking between them (or both)
| Concern | mTLS | Tokens (JWT / OAuth2 client-credentials) |
|---|---|---|
| What it proves | Which workload holds the connection | What this specific request is authorized to do |
| Layer | Transport (TLS) | Application (HTTP header) |
| App code changes | None (sidecar handles it) | Middleware to validate/attach tokens |
| Encrypts traffic | Yes, always | Only if carried over TLS separately |
| Fine-grained scopes/claims | No | Yes |
| Revocation | Re-issue/expire cert | Short expiry + token introspection/revocation list |
| Typical rotation | Minutes to hours, automated | Minutes to hours, automated |
For an interview answer, the strongest close is: use mTLS as the baseline for every east-west connection in the mesh (it's cheap once the sidecar is in place and it stops network-level spoofing), and layer JWTs on top wherever a service needs to make an authorization decision that depends on more than "which workload is this" — for example, propagating the original end user's identity through five hops of a call chain.