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

Measure what limits a copied access token

Replay one of your own access tokens from a second terminal and measure each limit on it: audience, scope, lifetime, revocation and clean logs, then confirm sender-constrained tokens are not offered yet.

Partly readyUses your lab tenant

The lesson

Builds on: Token revocation.

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. Get a short-lived token for Ava

    Recorded as oauth.token succeeded for lab-printer about [email protected].

  2. Use the copy from a second terminal

    Recorded as oidc.userinfo succeeded (userinfo_served) for lab-printer about [email protected].

  3. Revoke the copied token

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

Request console

Requests in this lab can be sent from this page to your tenant: open one and choose Send. Fill in the values below first. They stay in this page's memory and are gone when you leave; secrets are never stored or sent anywhere except the request you send.

Setup

  1. Choose Lab Photos as the lab tenant and press Start.

  2. In Access Token Management, create lab-tmp-short (Signed JWT, current ES256 key, Maximum lifetime 300) and assign it to lab-printer.

  3. Load ISSUER, CLIENT_ID and CLIENT_SECRET for lab-printer, API_ID and API_SECRET for lab-photo-api (exported for the toolkit), and the helpers from Present an access token correctly.

  4. Start three local APIs, each logging its decisions to a file you can search later:

    • the photo API, validating locally: btl-lab resource --mode jwt | tee ~/lab-photo-api.log

    • the same API, introspecting: btl-lab resource --mode introspect --port 8768 | tee ~/lab-photo-api-introspect.log

    • a sharing API: btl-lab resource --mode jwt --audience https://share.lab.test --port 8767 | tee ~/lab-share-api.log

Walkthrough

  1. Get a token: authorize "openid photos.read", sign in as Ava, exchange. Copy the value of $TOKEN into a second terminal (or a second computer that can reach your APIs) as COPY. You are copying your own token, exactly as a log or proxy would.

  1. Replay it from the second terminal:

curl -s -o /dev/null -w 'UserInfo %{http_code}\n' "$ISSUER/oidc/userinfo" -H "Authorization: Bearer $COPY"
curl -s -o /dev/null -w 'Photo API %{http_code}\n' http://127.0.0.1:8766/photos -H "Authorization: Bearer $COPY"

Both return 200.

Why it matters: a bearer token works for whoever presents it. Nothing in the request says who that is, so the copy is indistinguishable from the printer.

  1. Measure where and what the copy can do:

curl -s -o /dev/null -w 'Sharing API %{http_code}\n' -X POST http://127.0.0.1:8767/shares -H "Authorization: Bearer $COPY"   # 401, wrong_audience
curl -s -o /dev/null -w 'Upload %{http_code}\n' -X POST http://127.0.0.1:8766/photos -H "Authorization: Bearer $COPY"        # 403, insufficient_scope

Why it matters: audience restriction bounds where a copy works, and minimal scope bounds what it can do there.

  1. Measure how long it works. Read exp with btl-lab decode "$COPY" and compare it with date +%s. Then revoke it from the first terminal:

curl -s -o /dev/null -w 'revoke %{http_code}\n' -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/revoke" --data-urlencode "token=$TOKEN" -d token_type_hint=access_token

From the second terminal, call all three again over the next minute. UserInfo refuses at once with 401 invalid_token. The introspecting API refuses once its cached answer expires. The locally validating photo API keeps answering 200 until exp.

Why it matters: revocation reaches only APIs that ask the authorization server. A five-minute lifetime is what bounds the copy at an API that validates locally.

  1. Check your own logs for copies. Search every log file for the token:

grep -c "$TOKEN" ~/lab-photo-api.log ~/lab-photo-api-introspect.log ~/lab-share-api.log

Every count is 0, while the files hold jti, client_id, route, outcome and reason for each decision.

Why it matters: logs are the most common place copies escape. A record that a call failed with invalid_token tells a developer what they need without the token.

  1. Look for sender-constrained tokens in the metadata:

GET$ISSUER/.well-known/oauth-authorization-server Open in console
GET $ISSUER/.well-known/oauth-authorization-server HTTP/1.1

There is no dpop_signing_alg_values_supported and no tls_client_certificate_bound_access_tokens. This tenant issues bearer tokens only (G14, G8).

Why it matters: with a token bound to a key the printer holds, step 2 would fail from the second terminal, because the copy comes without the private key.

  1. Read what detection could see. In Audit, find the two oidc.userinfo events from steps 2 and 4. They show the same client and subject. Nothing in them tells you the second call came from another terminal.

Note: Audit does not yet record network or device context for protocol events (G37), so a replay from a new place looks like ordinary use.

Break it

None is needed beyond the replay itself: the lab uses only your own token, from your own terminals, against your own tenant and local APIs.

Check your work

Press Check my progress. The checks look for, in order: the token for Ava, oidc.userinfo with the copy, and oauth.revoke with access_token_found.

The refused UserInfo call with the revoked copy appears in Logs as invalid_token, not in Audit, because a revoked token no longer identifies a client.

Cleanup

  1. Assign Default access tokens back to lab-printer, then delete lab-tmp-short.

  2. Stop the three APIs and delete their logs: rm ~/lab-photo-api.log ~/lab-photo-api-introspect.log ~/lab-share-api.log. Run unset TOKEN COPY in both terminals.

Missing infrastructure

  • G14 DPoP. With DPoP-bound access tokens, the token carries a cnf claim with the key's thumbprint, every request carries a fresh proof signed with the printer's private key, and step 2 from the second terminal fails because it holds no key. The photo API would check the proof as part of validation.

  • G8 Mutual TLS client authentication and certificate-bound tokens. The token would be bound to the printer's TLS client certificate, and an API reached without that certificate would refuse the copy.

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