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

OAUTH 2.0 · LAB

Bind an access token to a client certificate

Compute your certificate's x5t#S256 thumbprint, plan a token whose cnf holds it and a resource that compares it, and see today why an unbound client credentials token works for anyone.

PlannedUses your lab tenant

The lesson

Builds on: Authenticating a client with a certificate.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Planned. The core of this lab waits on platform features that are not built yet. The planned walkthrough shows exactly how it will run; Do today is a real exercise you can do now.

Setup

  1. Keep the CA, orders.key, orders.crt and ORDERS_ID from Authenticate the order service with a client certificate. Read lab-print-orders's secret: read -rs ORDERS_SECRET; export ORDERS_SECRET.

  2. Add the Base64url helper.

b64url() { base64 | tr '+/' '-_' | tr -d '=\n'; }
  1. Planned (G8): on lab-tmp-mtls-orders, turn on Certificate-bound access tokens (tls_client_certificate_bound_access_tokens: true) and use an access token manager with the jwt format.

  2. Planned (G3, G57): the sample photo API offers a print jobs resource on the mutual TLS alias host, $MTLS_RESOURCE/print-jobs.

Planned walkthrough

  1. Compute the thumbprint you expect. This step runs today.

openssl x509 -in orders.crt -outform DER | openssl dgst -sha256 -binary | b64url; echo

Why it matters: "Binding the token to the certificate". x5t#S256 is the SHA-256 hash of the certificate's DER encoding, in Base64url without padding.

  1. Get a token over the alias with the certificate, as in the previous lab. token_type is still Bearer. Decode it with btl-lab decode "$TOKEN": cnf["x5t#S256"] equals step 1. Discovery shows tls_client_certificate_bound_access_tokens: true.

Why it matters: mutual TLS adds no new token type. The binding lives in cnf, and the connection the token must travel on.

  1. Introspect it with btl-lab introspect "$TOKEN". The response carries the same cnf.

Why it matters: an opaque token carries the binding through introspection.

  1. Call the resource over mutual TLS with the same certificate.

curl -s --cert orders.crt --key orders.key "$MTLS_RESOURCE/print-jobs" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"order":"book-5562","product":"hardcover-a4"}'

The job is accepted.

Why it matters: "Checking the certificate at the API". The API compares one thumbprint and leaves chain validation to the authorization server.

  1. Fill in the lesson's DPoP and mutual TLS table from what you saw in this lab and the DPoP labs.

Do today

  1. Run Planned walkthrough step 1, then compare it with OpenSSL's own fingerprint.

openssl x509 -in orders.crt -noout -fingerprint -sha256

It is the same hash, written in hex with colons instead of Base64url.

  1. Get an ordinary client credentials token for lab-print-orders with its secret.

TOKEN=$(curl -s -u "$ORDERS_ID:$ORDERS_SECRET" "$ISSUER/oauth/token" -d grant_type=client_credentials -d scope=prints.create | jq -r .access_token)
btl-lab decode "$TOKEN"

The claims have no cnf.

  1. Copy the token to a second environment that never had your certificate or secret, such as another machine or a cloud shell, and introspect it there with btl-lab introspect "$TOKEN". It is active. The resource has no way to tell who presented the token: this is the bearer property certificate binding removes.

  2. See the rule from "When a proxy ends the TLS connection" from the tenant's side. Send your own certificate in a Client-Cert header, the way a proxy would forward it.

curl -s "$ISSUER/oauth/token" -H "Client-Cert: :$(openssl x509 -in orders.crt -outform DER | base64 | tr -d '\n'):" \
  -d grant_type=client_credentials -d client_id=$ORDERS_ID -d scope=prints.create | jq .

The answer is 401 invalid_client, and Audit shows oauth.token rejected unsupported_auth_method. The tenant gives a client-supplied certificate header no authority at all, which is exactly what the lesson asks of anything behind a proxy.

Break it

These run once the gaps close.

  1. Call the resource with the bound token over the ordinary host, with no certificate. Expect 401 with WWW-Authenticate: Bearer error="invalid_token".

  2. Call it over mutual TLS with the renewed certificate from the next lab. Expect 401 invalid_token: a different certificate has a different thumbprint.

Check your work

Today, Audit shows oauth.token succeeded for lab-print-orders, the introspection by lab-photo-api, and oauth.token rejected unsupported_auth_method for the header attempt. Once the gaps close, Audit also shows a token issued with an x5t#S256 binding and resource requests accepted and refused.

Cleanup

  1. Revoke the client credentials token: curl -s -u "$ORDERS_ID:$ORDERS_SECRET" "$ISSUER/oauth/revoke" -d token=$TOKEN.

  2. Keep the certificate files for the next lab.

Missing infrastructure

  • G8 and G27: mutual TLS client authentication, certificate validation and the tls_client_certificate_bound_access_tokens setting.

  • G57: the mutual TLS alias host. The edge must remove any client-supplied certificate header, send its own only when a certificate was presented, and the tenant must accept that value only from the edge. These rules come straight from the lesson's proxy section and need their own tests.

  • G3: a sample resource that compares the connection's certificate thumbprint with cnf.

  • Once these exist, the Planned walkthrough runs as written.

Back to all labs

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

The Lab