Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

Certificate-bound access tokens

The order service now authenticates to the print lab with its certificate. The token it receives, though, is still an ordinary bearer token. If the print lab's API writes it to a log, or a misconfigured proxy records it, anyone holding a copy can submit print jobs as the printer until it expires fifteen minutes later.

The certificate can close that gap too. The authorization server already knows which certificate the token was requested with, so it can tie the token to that certificate and let the API insist on seeing it again.

Binding the token to the certificate

All domains, identifiers, and tokens in these examples are fictional. The order service's token request arrives over a mutual TLS connection, as in Authenticating a client with a certificate. The authorization server takes the client certificate from its TLS layer, calculates a SHA-256 hash of the certificate's binary DER encoding, and records that certificate thumbprint with the token. The response looks the same as before:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "demo-lab-access-token-3",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "print-jobs.write"
}

Unlike DPoP, mutual TLS adds no new token type. The order service sends the token in an ordinary Bearer header. What changes is the connection it must use. Decoded, the token's claims carry the binding:

{
  "iss": "https://auth.printlab.example",
  "sub": "printer-orders",
  "aud": "https://api.printlab.example",
  "client_id": "printer-orders",
  "scope": "print-jobs.write",
  "iat": 1790881200,
  "exp": 1790882100,
  "jti": "demo-lab-token-id-3",
  "cnf": {
    "x5t#S256": "upcMvPttuVZhYLYqzpqiqONaXZb2mD-2bd4QPsKEw2M"
  }
}

As with DPoP, the cnf claim says what a presenter of this token must prove. Its x5t#S256 member holds the certificate thumbprint, encoded as Base64url without padding. The token was issued at 19:00 UTC on 1 October 2026 and expires fifteen minutes later. If the token were opaque, the API would receive the same cnf member in an introspection response.

The print lab advertises the capability with tls_client_certificate_bound_access_tokens set to true in its metadata, and a client can declare the same setting in its registration to say it intends to use bound tokens. The binding does not depend on how the client authenticated. A client using private_key_jwt over a mutual TLS connection could receive a bound token too.

Checking the certificate at the API

To submit a job, the order service opens a mutual TLS connection to the print lab's API with the same certificate and sends:

POST /print-jobs HTTP/1.1
Host: api.printlab.example
Authorization: Bearer demo-lab-access-token-3
Content-Type: application/json

{ "order": "book-5562", "product": "hardcover-a4" }

The API validates the token exactly as it would any other. Then it asks its TLS layer for the certificate the client presented on this connection, calculates that certificate's SHA-256 thumbprint, and compares it with the token's cnf value. If they differ, or no certificate was presented, the request is refused:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"

Someone who copied the token from a log can send it, but only over a connection of their own. They cannot complete a handshake with the order service's certificate, because that needs the private key, so whatever certificate they present has a different thumbprint.

The API does not need to validate the certificate's chain or check who issued it. That was the authorization server's job when it authenticated the client. The API only needs to know that this connection was made with the same key as the token request, and the handshake has already proved that the client holds the private key for the certificate it presented.

The API does need to request a certificate in the first place. TLS asks for one during the handshake, before the token arrives, so an API cannot decide to ask only when it sees a bound token. APIs that accept certificate-bound tokens usually request certificates on every connection, or serve those requests from a separate host name.

When a proxy ends the TLS connection

Many APIs never see a TLS handshake. A load balancer or reverse proxy in front of them ends the client's TLS connection and forwards each request to the application over a separate, internal connection. For mutual TLS, the proxy is now the party that receives the client's certificate, and it has to pass the certificate along.

It usually does so in a request header. A common header for this, Client-Cert, is described in RFC 9440. It carries the certificate's DER encoding in Base64, between colons:

POST /print-jobs HTTP/1.1
Host: api.printlab.example
Authorization: Bearer demo-lab-access-token-3
Client-Cert: :MIIB+zCCAaCgAwIBAgIGXBp+IgnUMAoGCCqGSM49BAMCMDMx...:
Content-Type: application/json

The certificate is shortened here for display. The application computes the thumbprint from this header instead of from its own TLS layer.

That header is now as powerful as the certificate itself, which creates an obvious attack. An attacker with a stolen token connects to the proxy, presents any certificate or none, and adds their own Client-Cert header containing the order service's certificate, which is not secret. If the proxy passes that header through, the API sees a perfect match. The mutual TLS specification leaves this hop to each deployment, so the defense is a matter of deployment practice, set out in the header's own description and in current security guidance. It comes down to three rules:

  1. The proxy removes or overwrites any Client-Cert header that arrives from outside, and sends none at all when no client certificate was presented.
  2. The application accepts the header only from the trusted proxy, never from anything else that can reach it.
  3. The link between the proxy and the application is protected against eavesdropping and tampering, for example by its own authenticated TLS connection or a private network only the proxy can use.

The danger is that skipping the first rule does not break anything visible. Honest requests still work, so a missing sanitizing step can go unnoticed until someone uses it.

The order service opens a mutual TLS connection to the print lab's token endpoint alias with its client certificate and requests a token with the client credentials grant. The authorization server authenticates the client from the certificate and returns an access token whose cnf claim holds the certificate's SHA-256 thumbprint. The order service then opens a mutual TLS connection with the same certificate to the print lab's proxy and sends a print job with the token. The proxy removes any Client-Cert header sent by the client, adds the certificate it received, and forwards the request over a protected internal link. The API validates the token and compares the certificate's thumbprint with the token's cnf value before accepting the job. The order service opens a mutual TLS connection to the print lab's token endpoint alias with its client certificate and requests a token with the client credentials grant. The authorization server authenticates the client from the certificate and returns an access token whose cnf claim holds the certificate's SHA-256 thumbprint. The order service then opens a mutual TLS connection with the same certificate to the print lab's proxy and sends a print job with the token. The proxy removes any Client-Cert header sent by the client, adds the certificate it received, and forwards the request over a protected internal link. The API validates the token and compares the certificate's thumbprint with the token's cnf value before accepting the job.
The same certificate is used at the token endpoint and at the API. Behind a proxy, the API trusts the certificate the proxy forwards, so the proxy must discard any certificate header a client tries to supply.

Compared with DPoP

DPoP and certificate-bound tokens solve the same problem from different layers:

QuestionDPoPMutual TLS
Where is possession proved?In each HTTP request, with a signed proofIn the TLS handshake, once per connection
What is the token bound to?Any key pair the client generatesThe key in the client's certificate
What does cnf hold?jkt, a JWK thumbprintx5t#S256, a certificate thumbprint
How is the token sent?The DPoP scheme, with a proofThe Bearer scheme, over a mutual TLS connection
Can a captured request be replayed?Briefly, unless the server tracks identifiers or uses noncesNo. Each connection needs the private key
What about proxies?Proofs pass through unchanged in HTTP headersA proxy that ends TLS must forward the certificate safely
What about browser applications?Works with keys created in the browserImpractical, because browsers handle client certificates poorly

Mutual TLS suits backends that already manage certificates, such as the order service, and its replay protection comes with the connection itself. DPoP suits clients that cannot easily hold certificates, such as the phone app and browser applications. Both make a copied token worthless on its own, and both tie the token to something that, sooner or later, will be replaced. For a certificate that moment is certain, because every certificate has an expiry date.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2Someone copies the order service's certificate-bound token from a log and sends it over their own TLS connection. What happens at the print lab API?

QUESTION 2 OF 2A TLS-terminating proxy forwards every incoming header to the API unchanged, including any Client-Cert header a client sends. What is the risk?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity