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

Handle a shown-once secret and plan the move to private_key_jwt

See how the tenant generates, stores and reveals a client secret, how easily a request log captures it, and that the tenant does not yet accept a signed client assertion.

Partly readyUses your lab tenant

The lesson

Builds on: Client authentication methods.

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

Partly ready. Most of this lab runs today. Steps that wait on platform features are marked, and Missing infrastructure says what they need.

Your progress

Press Start before you begin. Only events your tenant records after that count, in the order below. Checking reads your tenant's Audit, so you need Audit read access in it.

  1. The secret travels with a token request

    Recorded as oauth.token succeeded for lab-print-orders.

  2. The secret travels with an introspection request

    Recorded as oauth.introspect succeeded (access_token_found) for lab-print-orders.

  3. The secret travels with a revocation request

    Recorded as oauth.revoke succeeded (access_token_found) for lab-print-orders.

  4. A client assertion is not accepted yet

    Recorded as oauth.token rejected (unsupported_auth_method) for lab-print-orders.

Setup

  1. Set ISSUER, and CLIENT_ID and CLIENT_SECRET for lab-print-orders, then press Start on this page.

Walkthrough

  1. The server generated the secret. echo ${#CLIENT_SECRET} shows a long random value you did not choose. Open OAuth > Clients > lab-print-orders > View: the secret appears nowhere, because the tenant keeps only a hash of it. Losing it means rotating it, not looking it up.

Why it matters: a secret used with client_secret_basic can be stored the way passwords are. A copy of the tenant's database would not reveal a working secret.

  1. Count where the secret travels. Run one token request, then introspect and revoke that token as the same client.

TOKEN=$(curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=client_credentials -d scope=prints.create "$ISSUER/oauth/token" | jq -r .access_token)
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -d "token=$TOKEN" "$ISSUER/oauth/introspect" | jq '{active, scope}'
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -d "token=$TOKEN" "$ISSUER/oauth/revoke"

Three requests, and each one carried the full secret.

  1. See what a debugging log would keep. Repeat the token request with -v and look at the request headers curl prints: Authorization: Basic .... Copy that value and decode it with printf '%s' '<value>' | openssl base64 -d -A. The secret is there in full. Clear your terminal scrollback afterwards.

Why it matters: anything that records requests in full, such as a misconfigured proxy or a verbose log, captures a credential that keeps working until someone replaces it.

  1. A client assertion is not accepted yet. Send the client credentials grant with a placeholder in the client assertion parameters and no Basic header.

curl -s -i -d grant_type=client_credentials -d "client_id=$CLIENT_ID" \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer -d client_assertion=placeholder "$ISSUER/oauth/token"

The answer is 401 invalid_client, and Audit records reason unsupported_auth_method.

  1. Find the one aud value a client assertion for this tenant should carry.

curl -s "$ISSUER/.well-known/openid-configuration" | jq '{issuer, token_endpoint}'

Under current guidance, an assertion's aud is exactly issuer, never token_endpoint, and the client confirms the issuer matches the address it used to fetch this configuration.

Why it matters: a malicious server could publish a genuine server's token endpoint as its own. The issuer identifier cannot be passed off that way, because the client checks it against where it found the configuration.

  1. Compare what a captured log is worth, using the lesson's checks.

Captured in a logStill works later?Why
The Basic header from step 3yes, until the secret is rotatedthe secret itself travels
A private_key_jwt assertionnoit expires within a minute, its jti is single use, and it holds no key

Planned walkthrough

These steps need client assertion verification (G8) and a client key set (G56). The private key never leaves your machine.

  1. Generate a key pair with the lab toolkit. It shows only the public JWK; check its thumbprint with btl-lab thumbprint public.jwk.

  2. Open OAuth > Clients > lab-print-orders, set Authentication method to private_key_jwt, register the public JWK (or a jwks_uri), and choose the allowed algorithms.

  3. The toolkit, acting as your own client, signs a client assertion with header typ: client-authentication+jwt and your kid, iss and sub equal to $CLIENT_ID, aud equal to the tenant's issuer only, a 60 second lifetime and a fresh jti, and sends it with client_assertion_type. The answer is 200.

  4. Planned refusals, each with its own Audit reason: the old secret sent with Basic, an assertion whose aud is the token endpoint URL instead of the issuer, an expired assertion, the same jti twice, and an unknown kid.

Break it

Step 4 is the real refusal. No setting changes.

Check your work

Press Check my progress. The three successful requests in step 2 each appear in Audit with actor lab-print-orders, a reminder that each one carried the secret.

Cleanup

None. The secret you decoded in step 3 is still valid; the next lab rotates it.

Missing infrastructure

  • **G8, client authentication beyond client_secret_basic.** Verification of private_key_jwt and client_secret_jwt assertions, with a strict issuer-only aud, jti replay tracking, and Audit fields for the method and key ID.

  • G56, client JWKS. Registering a client's public keys as a jwks value or a jwks_uri the tenant fetches, with kid lookup, plus a toolkit command that creates a key pair locally and signs your own client's assertions so you never hand-assemble a signed token.

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