Thoryn
  • 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 and certificate-bound tokens — client presents an X.509 cert, the hub embeds the SHA-256 thumbprint as cnf.x5t#S256 in the access token, and the resource server rejects any presentation where the TLS cert thumbprint does not match the token's binding

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: cert present → access token decodes to a JWT containing cnf.x5t#S256; thumbprint matches the claim byte-for-byte; no cert on a required client → 400 invalid_client; no cert on an optional client → token without cnf; DPoP + mTLS on the same request → both bindings 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 — same indirection FAPI 2.0 deployments use behind a Layer 7 load balancer, for the same reason: 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. Operationally cleaner when you can't terminate mTLS on the resource server — most CDN-fronted setups, where TLS terminates at the edge and the origin sees plain HTTP.

mTLS binds the token to the cert from the transport handshake. Operationally cleaner when the infrastructure already does mutual TLS — service meshes (Istio, Linkerd, Consul Connect) where every workload-to-workload hop is mTLS by default; financial-partner networks where the regulator mandates mutual authentication; healthcare integrations with an existing cert hierarchy. Not using mTLS in those environments means inventing parallel infrastructure to do worse what the network already does well.

The two are not exclusive — our test verifies they coexist — and a FAPI 2.0 deployment that uses DPoP for browser-facing flows and mTLS for back-channel workload calls is a real shape the hub doesn't get in the way of.

Why this is nice for partner integrations

Most B2B integrations have the same arc. Two companies sign a contract; their security teams agree on a credential exchange; one side mints a long-lived shared secret; somebody emails it; somebody pastes it into a vault; six months later someone leaves the partner's company 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 do you authenticate workload-to-workload calls?", the answer "mTLS, with cert-bound access tokens, conforming to RFC 8705" is a sentence that ends a meeting. "Shared secret in an Authorization header, please rotate it every 90 days" 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.