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

Authenticating a client with a certificate

Every evening, the printer's order service gets a token from the print lab with the client credentials grant and submits the day's photo books. In Client credentials it proved itself with a client secret in a Basic header. The print lab now wants its partners off shared secrets. It already runs a small certificate authority of its own for partner systems, and it would like each partner to prove itself the way servers already do on every HTTPS connection: with a certificate and the private key behind it.

OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens, published as RFC 8705, describes how. It defines two ways to use a TLS client certificate as an OAuth client's credential, and a way to bind tokens to that certificate.

A certificate on both sides

In an ordinary HTTPS connection, only the server presents a certificate and proves, during the TLS handshake, that it holds the matching private key. Validating a certificate followed the client's side of those checks. In mutual TLS, often shortened to mTLS, which Service-to-service access mentioned for internal calls, the server also asks the client for a certificate. The client sends one and signs part of the handshake with its private key, and the server checks both before any HTTP request is exchanged.

All domains, identifiers, and tokens in these examples are fictional. The print lab issued the order service this certificate, shown in the same simplified decoding as What a certificate says:

Certificate:
    Serial Number: 5c:1a:7e:22:09:d4
    Signature Algorithm: ecdsa-with-SHA256
    Issuer: O=Printlab, CN=Printlab Partner CA 1
    Validity
        Not Before: Sep 15 00:00:00 2026 GMT
        Not After : Dec 14 23:59:59 2026 GMT
    Subject: O=Photo Printer, CN=orders.printer.example
    Subject Public Key Info:
        Public Key Algorithm: id-ecPublicKey
        Public-Key: (256 bit), curve P-256
    X509v3 extensions:
        Subject Alternative Name:
            DNS:orders.printer.example
        Key Usage: critical
            Digital Signature
        Extended Key Usage:
            TLS Web Client Authentication
        Basic Constraints: critical
            CA:FALSE

Three details matter here. The issuer is the print lab's own partner CA, a private PKI that only the print lab's systems trust, as Certificate authorities and chains described. The extended key usage allows TLS client authentication, not server authentication, so this certificate identifies a caller rather than a website. And the subject alternative name, orders.printer.example, is the name the print lab will look for.

As with private_key_jwt, the private key never travels. The difference is where the proof happens. A client assertion is a signed JWT inside the HTTP request. Mutual TLS proves possession of the key in the connection underneath, so the token request itself carries no credential at all.

Matching the certificate to a client

Over that connection, the order service sends its token request:

POST /token HTTP/1.1
Host: mtls.auth.printlab.example
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=printer-orders
&scope=print-jobs.write

There is no Authorization header and no secret in the body. The client_id is required, though. It tells the authorization server which registration to check the certificate against, rather than leaving it to guess the client from the certificate's contents.

The print lab registered the order service for the first of the two methods:

{
  "client_id": "printer-orders",
  "token_endpoint_auth_method": "tls_client_auth",
  "tls_client_auth_san_dns": "orders.printer.example",
  "grant_types": ["client_credentials"],
  "scope": "print-jobs.write"
}

With tls_client_auth, the server relies on a certificate authority. The TLS handshake has already shown that the client holds the private key and that the certificate chains to a CA the server trusts for this purpose. The server then compares the certificate with the one subject value the registration expects. A registration names exactly one: the subject distinguished name, or a single subject alternative name of type DNS, URI, IP address, or email. Here the certificate's DNS name matches orders.printer.example, so the order service is authenticated as printer-orders.

The second method, self_signed_tls_client_auth, needs no CA at all. The client creates its own self-signed certificate and registers the certificate itself, either in the registration's jwks value or in a key set published at its jwks_uri. Each entry is a JWK whose x5c member holds the certificate:

{
  "keys": [
    {
      "kty": "EC",
      "crv": "P-256",
      "x": "...",
      "y": "...",
      "x5c": ["MIIB..."]
    }
  ]
}

The values are shortened here. The server does not validate any chain for this method. It authenticates the client when the certificate presented in the handshake is exactly one of the certificates registered for that client.

Questiontls_client_authself_signed_tls_client_auth
Who vouches for the certificate?A CA the server trusts for client certificatesNo one. The registration itself is the trust
What does the registration hold?One expected subject valueThe certificates, or the address of a key set holding them
What does a renewal need?A new certificate with the same subject. The registration does not changeThe new certificate added to the registered set before it is used
Fits best whenAn organization or ecosystem already runs a PKIThere is no PKI and clients are few

Either way, a missing certificate or one that does not match the registration produces the same response as any other failed client authentication: invalid_client, with no hint about which part was wrong.

Separate addresses for mutual TLS

The request above went to mtls.auth.printlab.example, not to auth.printlab.example. The reason lies in how TLS works. A server must ask for a client certificate during the handshake, before it sees the HTTP request, so it cannot ask only the clients that intend to use one. Browsers react badly to being asked: they often show a certificate selection window to the person in front of them. The print lab's authorization server also serves sign-in and consent pages to browsers, and it does not want them disturbed.

So it offers separate addresses for clients that use mutual TLS and lists them in its authorization server metadata:

{
  "issuer": "https://auth.printlab.example",
  "token_endpoint": "https://auth.printlab.example/token",
  "token_endpoint_auth_methods_supported": [
    "client_secret_basic",
    "tls_client_auth",
    "self_signed_tls_client_auth"
  ],
  "mtls_endpoint_aliases": {
    "token_endpoint": "https://mtls.auth.printlab.example/token"
  }
}

mtls_endpoint_aliases holds alternative addresses for endpoints that clients call directly, such as the token, revocation, and introspection endpoints. A client that uses mutual TLS must use the alias when one is listed, and the ordinary address for any endpoint that has none. The authorization endpoint has no place there: the client never calls it directly, and the browsers that do visit it are never asked for a certificate.

Current security guidance recommends asymmetric client authentication, with mutual TLS and private_key_jwt as its examples, because the server then stores nothing that could be used to impersonate the client. Mutual TLS asks more of the infrastructure: certificates to issue and renew, and network equipment that has to pass certificate details along. In return, the same certificate can do a second job: binding the print lab's access tokens to the order service.

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 2The order service is registered for tls_client_auth with tls_client_auth_san_dns set to orders.printer.example. Which certificate authenticates it?

QUESTION 2 OF 2The print lab's metadata lists an mtls_endpoint_aliases entry for the token endpoint. Why does a client using mutual TLS send its token requests there?

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