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.
- G14 DPoP
- G8 Client authentication beyond `client_secret_basic` and `none`
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.
Sign in to start this lab and check your progress. Log in or create an account.
Get a short-lived token for Ava
Recorded as
oauth.tokensucceeded forlab-printerabout[email protected].Use the copy from a second terminal
Recorded as
oidc.userinfosucceeded (userinfo_served) forlab-printerabout[email protected].Revoke the copied token
Recorded as
oauth.revokesucceeded (access_token_found) forlab-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
Choose Lab Photos as the lab tenant and press Start.
In Access Token Management, create
lab-tmp-short(Signed JWT, current ES256 key, Maximum lifetime300) and assign it tolab-printer.Load
ISSUER,CLIENT_IDandCLIENT_SECRETforlab-printer,API_IDandAPI_SECRETforlab-photo-api(exported for the toolkit), and the helpers from Present an access token correctly.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.logthe same API, introspecting:
btl-lab resource --mode introspect --port 8768 | tee ~/lab-photo-api-introspect.loga sharing API:
btl-lab resource --mode jwt --audience https://share.lab.test --port 8767 | tee ~/lab-share-api.log
Walkthrough
Get a token:
authorize "openid photos.read", sign in as Ava,exchange. Copy the value of$TOKENinto a second terminal (or a second computer that can reach your APIs) asCOPY. You are copying your own token, exactly as a log or proxy would.
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.
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.
Measure how long it works. Read
expwithbtl-lab decode "$COPY"and compare it withdate +%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.
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.
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.1There 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.
Read what detection could see. In Audit, find the two
oidc.userinfoevents 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
Assign Default access tokens back to
lab-printer, then deletelab-tmp-short.Stop the three APIs and delete their logs:
rm ~/lab-photo-api.log ~/lab-photo-api-introspect.log ~/lab-share-api.log. Rununset TOKEN COPYin both terminals.
Missing infrastructure
G14 DPoP. With DPoP-bound access tokens, the token carries a
cnfclaim 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.