← All posts
mtls

mTLS for machine credentials: when a Bearer token isn't binding enough

mTLS for machine credentials: when a Bearer token isn't binding enough

Every backend engineer has shipped a Bearer token or an API key for machine auth. Far fewer have shipped mTLS. Both belong to the same arc — how does the server know who's calling — but they answer different questions, and the Bearer-or-key default is silently wrong on the surfaces where a leaked credential equals a takeover.

This is a short note on what mTLS actually adds, when it earns its weight, and what Authaz issues — for both M2M OAuth credentials and admin API keys.

A Bearer token (or API key) is a bearer instrument

The name is the design. Whoever holds the credential is the caller — full stop. There's no binding to the network connection, no proof of possession, no challenge at the TLS layer. A token or key lifted from a log line, a leaked HAR file, a misconfigured proxy, a .env committed by accident, or a misbehaving SDK can be replayed from anywhere, by anything, until it expires (or, for long-lived API keys, until someone notices).

Bearer (signed JWT over TLS)
POST /api/orders HTTP/1.1
Host: api.acme.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/json
 
{ "sku": "SKU-001", "qty": 1 }
Server checks:
  1. signature(JWT, JWKS)   ✓
  2. exp / aud / iss        ✓
  3. accept request as:     sub
mTLS (cert-bound JWT over TLS 1.3)
POST /api/orders HTTP/1.1
Host: api.acme.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
X-Client-Cert: <leaf chain from TLS handshake>
 
{ "sku": "SKU-001", "qty": 1 }
Server checks:
  1. signature(JWT, JWKS)                ✓
  2. exp / aud / iss                     ✓
  3. SHA256(client.cert) == cnf.x5t#S256 ✓
  4. accept request as: SPIFFE URI in SAN

The Bearer check proves the token is intact. The mTLS check proves the caller holds the private key whose certificate is bound to this token. Two different guarantees.

What mTLS actually adds

A client cert is presented during the TLS handshake, before the first HTTP byte. The server gets cryptographic proof that the caller holds the private key matching the certificate's public key — a property no payload-level token can give you, because the token is the payload.

The benefits stack:

  • Identity is in the cert, not in a header the client asserts. Subject, SAN, SPIFFE URI — server-controlled at issuance.
  • No shared secret on the wire. A client_secret is a password; an mTLS handshake is a challenge-response.
  • Bound to the connection. A leaked token alone is useless without the matching private key. Compromise has to land both.
  • Revokable at the trust anchor. Issue a new CA, retire the old one, every cert under it dies.

When a signed Bearer is enough

For user-facing surfaces — browser sessions, mobile apps, anything where a human is at the keyboard — a signed Bearer over HTTPS is the right default. The token is short-lived, the user can be re-authenticated, and forcing a client cert into a login flow is a UX regression people will route around. mTLS at this layer is solving the wrong problem.

When mTLS earns its weight

mTLS earns its weight on machine surfaces, especially ones with a long blast radius if a credential leaks:

  • Service-to-service inside a tenant, where a single compromised pod shouldn't be able to call every internal API as anyone.
  • AI agents calling third-party APIs — long-running workloads with credentials sitting in process memory and tool-use traces.
  • B2B partner integrations, where the token traverses partner infra, partner logs, and partner SDKs before reaching your server.
  • Admin API keys that mutate configuration — provisioning tenants, rotating credentials, granting roles. A leaked admin key is a supply-chain primitive; a leaked admin key bound to a cert is much less interesting to whoever finds it.
  • Regulated workloads (PCI, HIPAA-adjacent, financial), where "the credential was leaked but not the cert" is a meaningfully different answer to a regulator than "the credential was leaked."
  • Webhooks back to a customer, where you want the customer to verify you called them, not just someone with this secret called them.

If a stolen credential equals a takeover, you're in mTLS territory.

Certificate-bound access tokens (RFC 8705 §3)

This is the pattern worth memorizing. The token endpoint accepts a client cert. The issued access token carries a cnf.x5t#S256 claim — the SHA-256 thumbprint of that cert, base64url-encoded. Resource servers verify the token and compare the thumbprint to the cert the client just presented at the TLS layer:

{
  "sub":   "cred_01HZX7…",
  "aud":   "api.acme.com",
  "exp":   1773292800,
  "scope": "orders.write",
  "cnf":   { "x5t#S256": "Y9C2lkF7v…" }
}

You keep the Bearer ergonomics — caching, JWKS verification, opaque header handling — and you get TLS-layer binding for free. A token lifted from a log is useless on any connection that can't present the matching cert.

RFC 8705 calls this "Mutual TLS Client Certificate-Bound Access Tokens," and it's the sweet spot for almost every M2M case that's outgrown raw Bearer.

Why this is hard everywhere else

mTLS is famous for being correct and infamous for being unshippable. To do it yourself, you have to:

  • Run a certificate authority — issue, sign, distribute, rotate, retire.
  • Ship trust bundles to every resource server without breaking a deploy.
  • Build renewal that doesn't drop traffic when a cert flips.
  • Bind tokens to certs so a single leak doesn't undo the whole effort.
  • Do the same thing twice — once for the data plane, again for the admin keys you use to manage it.

Most teams either skip mTLS entirely and accept Bearer's leak risk, or build half of it on a Friday and live with the duct tape forever. The platforms that do offer mTLS usually leave the CA and the trust distribution to you — which is exactly the part that fails in production.

That's the gap. mTLS isn't a hard cryptography problem. It's a hard operations problem, and it's why most M2M auth is still Bearer with crossed fingers.

Why Authaz is the right place to do mTLS

We treat mTLS as a first-class primitive of the platform, not a feature flag bolted onto OAuth. That gives you four things you won't get by stitching it together yourself:

  • A workload CA per tenant, with zero ops. You don't run cert infrastructure. Authaz issues, rotates, retires, and distributes — across regions, behind a single trust URL your services pin once. Your code never has to know what a PEM file is.
  • One trust model for tokens and admin keys. Most vendors give you mTLS on the OAuth token endpoint, or on their management API, never both. Authaz gives you both with the same binding semantics. A leaked admin key has the same blast radius as a leaked data-plane token: zero, if the cert isn't with it. That's the entire point of mTLS, and it shouldn't stop at the data plane.
  • Workload identity baked in. Every cert Authaz issues carries a SPIFFE identity in its SAN — server-built, untamperable. Your resource servers stop asking "is this token valid?" and start asking "is this the right caller for this resource?" — a question Bearer can't answer at all.
  • Opt-in per credential. mTLS isn't a global switch you flip on a Friday and pray. Promote one sensitive integration — your billing webhook, your agent's API key, the credential that calls your most regulated endpoint — onto cert binding while everything else stays on Bearer. No big-bang migration. No coupled rollout. No team-wide change to your SDK.

Bearer stays the default. mTLS is one flag away on the credentials that earn it. Your SDK doesn't change. Your application code doesn't change. The only thing that changes is what happens when a credential leaks: nothing.

What we've already absorbed for you

When teams roll mTLS themselves, the same five mistakes show up in roughly this order — each one has cost somebody an outage or an incident report:

  • Pinning a leaf cert instead of a trust anchor, then suffering a planned outage on the next rotation.
  • Rotating at expiry instead of before it, watching traffic drop silently as certs flip mid-flight.
  • Stretching cert lifetimes to dodge rotation pain, undoing the security model to escape the operational one.
  • Putting PII in a SAN to make audit easier, then leaking it into TLS logs and certificate transparency.
  • Wiring cert binding into the data plane and forgetting the control plane, where the highest-blast-radius credential you own still sits naked.

These aren't theoretical. They're the reason most teams don't ship mTLS even when they know they should. Authaz's defaults make them mistakes you don't have to make — and never have to think about.

TL;DR

Bearer answers "is this credential valid?" Good enough for browser sessions and most user-facing auth.

mTLS answers "is this credential valid and does the caller hold the key it's bound to?" That's the answer you want on every machine surface where a leaked credential would mean a takeover — from your service-to-service calls to your AI agents to the admin keys that mutate your configuration.

Most teams skip mTLS because the path to "correct" runs through operating your own CA, distributing trust at scale, and rotating certs without dropping a request. Authaz takes all of that off your plate — one flag per credential, Bearer ergonomics on top, TLS-bound proof of possession underneath, the same trust model for your data plane and your control plane.

If a leaked credential should never equal a takeover in your stack, come talk to us. We'll show you what mTLS looks like when the hard parts are already done.

Security isn't a premium feature. · Older post →