mTLS client authentication (RFC 8705)¶
For high-trust integrations, you can authenticate to the auth service
by presenting an X.509 client certificate on the TLS handshake
instead of (or in addition to) a client_secret. The auth service
verifies the cert against the platform's registered trust store and
issues an access token bound to that cert per
RFC 8705 — OAuth 2.0 Mutual-TLS Client Authentication.
Why use it¶
- A leaked
client_secretis portable; a leaked key file is harder to exfiltrate and easier to detect. - Issued tokens carry a
cnf.x5t#S256claim. Resource servers configured to verify token binding will reject the token if a different cert (or no cert) is presented on the resource call. - mTLS is a standard control for procurement / compliance reviews ("HSM-backed key material", "FIPS-validated cipher suites", etc.).
How to opt in¶
- Generate a client key + self-signed cert (or get one issued by your
PKI). Example:
openssl req -x509 -nodes -newkey ec -pkeyopt ec_paramgen_curve:P-256 -days 365 -subj "/CN=acme-corp-production" -keyout client.key -out client.crt - Send the certificate (
client.crt— not the key) to your platform operator. They upload it through the admin UI's Client certificates (mTLS) card on the integration page. - Update your integration's TLS config to present
client.key+client.crtto the auth service's mTLS listener.
Once at least one active certificate is on file, the auth service
requires a cert on every token request from this client. Calls
without one return 401 mtls_required. There is no fallback to
client_secret — that's the whole point.
You can register multiple certs in parallel. Use this for staged rollovers: register the new cert, switch the integration over, then revoke the old one. The trust store is union — any active cert matches.
Token binding¶
Every token issued via mTLS carries the standard RFC 8705 claim:
{
"sub": "client-id",
"org_id": "...",
"scopes": ["..."],
"cnf": {
"x5t#S256": "qrvM3e7_ABEiM0RVZneImaq7zN3u_wARIjNEVWZ3iJk"
}
}
x5t#S256 is the base64url-encoded SHA-256 of the DER form of the
client certificate. Resource servers that participate in mTLS
sender-constraint can verify the token holder is the same party that
minted it: compute x5t#S256 of the cert presented on the resource
call and compare against the claim.
The binding also flows through the urn:ietf:params:oauth:grant-type:token-exchange
grant — delegated user tokens minted via mTLS carry the same cnf.
Endpoint configuration¶
The mTLS endpoint runs on a separate port from the regular HTTP service. In deployed environments:
| Setting | Default | What it does |
|---|---|---|
MTLS_ENABLED |
false |
Enables the mTLS HTTPS listener at boot |
MTLS_PORT |
3443 |
Port for the mTLS listener |
MTLS_TLS_CERT_PATH |
— | Server cert PEM (auth service's own TLS cert) |
MTLS_TLS_KEY_PATH |
— | Server cert key |
The listener configures the TLS handshake with
requestCert: true, rejectUnauthorized: false — any cert is accepted
at the TLS layer and validated by the application using the
registered fingerprints. That way a bad cert returns a structured
401 invalid_client instead of a connection drop.
Curl example¶
curl -X POST https://platform.example.com:3443/v1/auth/oauth/token \
--cert client.crt --key client.key \
--data grant_type=client_credentials \
--data client_id=<your-client-id>
# no client_secret — cert is the credential
When to also keep a secret¶
You don't need to. Opting in to mTLS revokes the secret-based path for this client. If you'd like a fallback (mTLS for production, secret for local dev), use two separate OAuth2 clients — one with a cert, one without — and switch which one the deploy uses by environment.
Cert rotation¶
Certificate rotation is the same shape as secret rotation:
- Register the new cert via the admin UI.
- Switch your integration to present the new cert.
- Revoke the old cert from the admin UI.
There's no overlap-window knob here — both certs are simultaneously valid until you revoke the old one. Tokens minted under the old cert keep working until they expire on their own TTL; if you need immediate invalidation, also rotate the auth service's signing key (out of scope for this doc).