Skip to content
  • oauth2
  • security
  • mtls
  • fapi
  • wif

mTLS client auth and cert-bound tokens — OAuth2 for workloads, not browsers

OAuth2 was invented for the browser. RFC 8705 retrofits it onto workloads — service meshes, financial counterparties, partner integrations. Here is what shipped and why it matters for B2B.

10 Jun 2026 · Mark Bakker

OAuth 2.0 was invented for the browser. RFC 6749 assumes a user agent that opens a redirect, types a password, and rides a session cookie home. None of that exists when the client is a workload — a service mesh sidecar, a financial counterparty's middleware, a partner ETL job that runs at 04:00 with nobody watching. RFC 8705 is the workload-identity profile of OAuth: it retrofits the framework onto a world where the client is a process holding a certificate, not a browser holding a cookie. The Thoryn Authorization Hub speaks it natively, and for B2B integrations it is a quiet superpower. This post is what we built and what it buys you the moment a token leaks.

The workload-client problem

client_secret_basic — the default OAuth client authentication method — is a username and a password posted on every token request. It works. It is also not the kind of thing two regulated counterparties want on a contract whose internal-audit teams will read every clause out loud. The shared secret has to be agreed somewhere, rotated somewhere, stored somewhere on both sides. Every step is a place where a human pastes a password into a Slack channel — and a story ends with "we don't know how it leaked".

mTLS makes the transport the credential. The client presents an X.509 cert during the TLS handshake; the server's trust store decides whether the issuer is acceptable; the cert's subject DN, SAN, or thumbprint identifies the client. There is no password — just a key, and the key never leaves the client's HSM.

The bonus is that the client's PKI is already there. Your partner has a corporate CA. Their workloads already mint certificates. You don't have to invent a key-distribution flow with an out-of-band rotation procedure that nobody actually executes. The cert IS the credential, and somebody else has been managing its lifecycle for a decade.

mTLS client auth — hub embeds the cert thumbprint as cnf.x5t#S256; resource server rejects mismatches

Two things at once

RFC 8705 is, mechanically, two specifications stapled together that share an X.509 certificate but answer different questions.

Client authentication. Two new token_endpoint_auth_method values: tls_client_auth (the cert's subject DN or SAN matches a registered value) and self_signed_tls_client_auth (the cert's SHA-256 thumbprint matches a registered JWK). Both replace client_id + client_secret on /oauth2/token with a TLS-handshake-level proof.

Token binding. The issued access token carries cnf.x5t#S256 — the SHA-256 thumbprint of the presenting cert. The resource server, on every request, takes the cert from its TLS handshake, recomputes the thumbprint, compares. Mismatch → reject. The token has been moved off the connection it was minted for, and the resource server can tell.

You can use one half without the other; most customers turn on both because the operational model is identical.

What we implement

Per-client mTLS settings sit behind an mTLS-required flag — if true, every token request must present a certificate or the hub returns 400 invalid_client. Client metadata exposes tlsClientAuthSubjectDn and tlsClientAuthSanDns for tls_client_auth-style binding, and client_authentication_methods can include self_signed_tls_client_auth. Discovery advertises tls_client_certificate_bound_access_tokens: true.

The mTLS system test exercises the surface end-to-end. A cert present yields an access token with cnf.x5t#S256; the thumbprint matches the claim byte-for-byte. No cert on a required client returns 400 invalid_client; no cert on an optional client returns a token without cnf. DPoP and mTLS on the same request coexist without a server error.

The test container runs on plain HTTP, so a forwarded-cert header substitutes for the TLS handshake. In production, the cert arrives via gateway mTLS termination and is forwarded through the same header. This is the same indirection FAPI 2.0 deployments use behind a Layer 7 load balancer: the workload that terminates TLS is rarely the workload that issues the token.

The presenting-cert check

The interesting half of RFC 8705 is what happens at the resource server. The hub's job is to put the thumbprint in the token. The resource server's job is to re-check it on every request.

Concretely: validate signature, claims, expiry as you would any bearer token. Then read cnf.x5t#S256, take the certificate from this TLS connection, compute the SHA-256 thumbprint of its DER-encoded form, compare. Mismatch → 401 invalid_token.

The consequence is that the access token can no longer be replayed onto a different TLS connection. If a token leaks — through a log, through a bug, through a misconfigured proxy — the attacker can't use it without the private key. A plain bearer token is, by definition, bearable: anyone holding it can present it. A cert-bound token is only valid in the hands of the workload whose key signed the handshake. The gap between "the token leaked" and "the attacker can use the token" is exactly the thing you wanted.

DPoP vs mTLS, revisited

We shipped DPoP earlier. Same goal, different layer.

DPoP binds the token to a key the client generates; the client signs a fresh JWT proof on every request. It is the cleaner choice when you can't terminate mTLS on the resource server — most CDN-fronted setups terminate TLS at the edge and the origin sees plain HTTP.

Certificate-bound tokens via mTLS fit naturally when the infrastructure already does mutual TLS. Service meshes (Istio, Linkerd, Consul Connect) run mTLS by default on every workload-to-workload hop. Financial-partner networks often mandate it. Healthcare integrations typically have an existing cert hierarchy. Not using mTLS in those environments means building parallel infrastructure to do worse what the network already does well.

The two are not exclusive — our test verifies they coexist. A FAPI 2.0 deployment using DPoP for browser-facing flows and mTLS for back-channel workload calls is a real shape the hub supports.

Why this is nice for partner integrations

Most B2B integrations have the same arc. Two companies sign a contract; security teams agree on a credential exchange; one side mints a shared secret; somebody emails it; somebody pastes it into a vault. Six months later someone leaves and nobody knows whether they still have access. The shared secret outlives the relationship that minted it.

mTLS is structurally different. The partner has a CA. Their CA issues certs with whatever lifetime their policy says. When an employee leaves, their cert is revoked at the CA, and the next handshake fails — not because we did anything, because the CA did. The credential's lifecycle follows the workload, not the relationship.

The hub admin doesn't manage the partner's keys. The hub admin registers a subject DN patternCN=*.partner-corp.com,O=Partner Corp — and the hub trusts any cert with that subject signed by the partner's registered CA. A new workload gets a fresh cert and works on the first try. A retired workload's cert eventually expires; nothing to revoke on our side.

When a financial counterparty asks how you authenticate workload-to-workload calls, the answer "mTLS, with cert-bound access tokens, conforming to RFC 8705" ends the meeting. Proposing a shared secret with a 90-day rotation schedule is the start of a much longer conversation.

If you have a partner integration that's currently shared-secret and you've been waiting for a moment to upgrade it, this is the moment. Request access and we'll walk you through the cert binding on both sides.