OAUTH 2.0 · LAB
Disconnect the printer and measure what stops working
Revoke tokens as the printer, see one client refused when it tries to revoke another's token, and measure what still works at an API that introspects and at one that does not.
Partly readyUses your lab tenant
The lesson
Builds on: Token introspection, Client credentials.
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.
- G40 Grant and consent inventory: list and revoke a user's app grants ("connected applications"), app approval policy, tenant-wide app block
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.
Revoke Ava's refresh token as lab-printer
Recorded as
oauth.revokesucceeded (refresh_token_found) forlab-printer.See the revoked refresh token refused
Recorded as
oauth.tokenrejected (refresh_revoked) forlab-printer.Revoke only an access token
Recorded as
oauth.revokesucceeded (access_token_found) forlab-printer.Be refused revoking lab-printer's token as lab-print-orders
Recorded as
oauth.revokerejected (other_client_token) forlab-print-orders.Find a disabled client refused at its next refresh
Recorded as
oauth.tokenrejected (client_disabled) 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.
Confirm
lab-printeruses Default access tokens (Signed JWT) and has the refresh token grant. LoadCLIENT_IDandCLIENT_SECRETfor it,ORDERS_IDandORDERS_SECRETforlab-print-orders, andAPI_IDandAPI_SECRETforlab-photo-api, each secret withread -rs.Use the
authorize,exchangeandrefreshhelpers from Rotate a refresh token family, and add a revocation helper that prints only the status line and body:
revoke() { # $1 = token, $2 = hint; uses lab-printer unless REVOKE_AS is set to "id:secret"
curl -s -w '\nHTTP %{http_code}\n' -u "${REVOKE_AS:-$CLIENT_ID:$CLIENT_SECRET}" "$ISSUER/oauth/revoke" \
--data-urlencode "token=$1" ${2:+-d token_type_hint=$2}
}
Run two photo APIs in separate terminals, both expecting the default audience:
local validation:
btl-lab resource --mode jwton port 8766introspection:
btl-lab resource --mode introspect --port 8767(withAPI_IDandAPI_SECRETexported)
Walkthrough
Connect.
authorize "photos.read offline_access", sign in as Ava,exchange, and keepREFRESH. Call both APIs atGET /photos:200and200.
Disconnect by revoking the refresh token, the credential that keeps the connection alive.
POST$ISSUER/oauth/revoke
Open in console
POST $ISSUER/oauth/revoke HTTP/1.1
Content-Type: application/x-www-form-urlencoded
token=$REFRESH&token_type_hint=refresh_tokenIn the console, enter lab-printer's client ID and secret, or run revoke "$REFRESH" refresh_token. Returns 200 with an empty body.
Why it matters: the answer carries nothing beyond its status. The printer wanted the token to stop working, and it has.
Send exactly the same request again:
200. Revoke a made-up string:revoke "no-such-token"also returns200.
Why it matters: unknown and already revoked tokens get the same answer, so revocation is safe to repeat after a timeout, and the answer reveals nothing about which strings are tokens.
See what the revocation reached.
refresh "$REFRESH":invalid_grant, Audit reasonrefresh_revoked.The introspecting API on 8767, after its cached answer expires (about a minute):
401.The JWT API on 8766: still
200.
Why it matters: revoking a refresh token also revoked the access tokens from its grant, but only an API that asks the authorization server can see that. Local validation accepts the JWT until its exp.
Measure the window. Read
expwithbtl-lab decode "$TOKEN"and subtract the time of step 2:echo $(( exp - revoked_at ))seconds. With the default one-hour lifetime the window is long; the lesson's ten-minute token gave six minutes.
Why it matters: short lifetimes keep this window small, and an API can introspect even a JWT before a destructive operation such as a deletion.
Revoke only an access token. Start a fresh connection (step 1), then
revoke "$TOKEN" access_token.refresh "$REFRESH"still succeeds.
Why it matters: revoking an access token may leave the refresh token working. A client that wants to end everything sends the refresh token.
Try to end another client's access. With a current
lab-printerrefresh token inREFRESH, revoke it while authenticating as the back-office job:
REVOKE_AS="$ORDERS_ID:$ORDERS_SECRET" revoke "$REFRESH" refresh_token
Returns 400 with {"error": "unauthorized_client"}, and Audit records oauth.revoke rejected with other_client_token. Introspect the token as lab-printer: it is still active.
Why it matters: one client may not end another's access. The tenant refuses with an error, as RFC 7009 describes, so the caller learns it cannot do this, while unknown strings still get 200.
Disconnect in the right order. The printer marks the connection disconnected first, keeps the refresh token only until the server answers
200, and only then deletes it:
if revoke "$REFRESH" refresh_token | grep -q 'HTTP 200'; then unset REFRESH; echo "revoked and deleted"; else echo "kept in the pending queue; retry later"; fi
Why it matters: deleting first would leave a working token at the photo service and no way left to revoke it. A 503 means the token may still work.
End access from the authorization server's side. Start a fresh connection, then in Clients set
lab-printerto disabled and save. Runrefresh "$REFRESH":401 invalid_client, Audit reasonclient_disabled. Enable it again: the tokens issued before were revoked by the change, so the printer has to connect again.
Why it matters: the provider can end access even when the client is offline or no longer trusted, and the client finds out only at its next request.
Restore: make sure lab-printer is enabled.
Break it
Revoke with a wrong secret: REVOKE_AS="$CLIENT_ID:wrong-secret-wrong-secret-wrong-secret" revoke "$TOKEN". Returns 401 invalid_client, the same refusal as at the token endpoint. Audit records it against lab-printer.
Check your work
Press Check my progress. The checks look for, in order: oauth.revoke with refresh_token_found, oauth.token rejected with refresh_revoked, oauth.revoke with access_token_found, the refused oauth.revoke with other_client_token from lab-print-orders, and oauth.token rejected with client_disabled.
Audit also shows oauth.revoke succeeded with token_not_found for the made-up string, and tenant.oauth.clients.update twice for disabling and enabling lab-printer.
Cleanup
Confirm
lab-printeris enabled.Stop both
btl-lab resourceterminals. Rununset TOKEN REFRESH RESP REVOKE_AS ORDERS_SECRET API_SECRET.
Missing infrastructure
G40 Connected applications. There is no page where Ava sees each client she approved, what it can access and when it was last used, and no administrator view of grants per user or client. Removing a grant there would end it and every family under it. Once it exists, step 9 becomes: sign in to
$ISSUER/accountas Ava, removelab-printer, and watch its next refresh fail withinvalid_grantwhile the client itself stays enabled for other people.