OAUTH 2.0 · LAB
Inventory and migrate a legacy client
Build the lesson's inventory from your tenant, move a legacy confidential client to PKCE and an exact redirect list, retire an unused client, and note what a report-only mode would add.
Partly readyUses your lab tenant
The lesson
Builds on: The implicit grant.
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.
- G52 Governance and usage reporting: ownership metadata, filterable last sign-in, per-client last use and usage inventory, report-only enforcement
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.
The legacy client redeems a code without PKCE
Recorded as
oauth.tokensucceeded forlab-tmp-greeting-cards.A verifier for a code issued without PKCE is refused
Recorded as
oauth.tokenrejected (pkce_failed) forlab-tmp-greeting-cards.Require PKCE for the legacy client
Recorded as
tenant.oauth.clients.updatesucceeded.A request without a challenge is now refused
Recorded as
oauth.authorizerejected (pkce_required) forlab-tmp-greeting-cards.Delete the unused client
Recorded as
tenant.oauth.clients.deletesucceeded.
Setup
Press Start on this page.
Create a temporary client from Web application named
lab-tmp-greeting-cards, type Confidential, with PKCE for confidential clients set to Optional and three redirect URIs:https://beyondthelogin.dev/lab/callback/,http://127.0.0.1:8765/cards/oldandhttp://127.0.0.1:8765/cards/unused. Tick Enabled, save, and generate its secret in Access Token Management.
Restore: step 6 of the walkthrough sets PKCE back to Required, and Cleanup deletes the client.
Create a second temporary client from Web application named
lab-tmp-old-uploader, with redirect URIhttp://127.0.0.1:8765/uploader/callback. Do not use it.Set
ISSUER,CARDS_IDandCARDS_SECRETforlab-tmp-greeting-cards,API_IDandAPI_SECRETforlab-photo-api, andenc.Give the legacy client some history: run two authorization code flows as Ava without any
code_challenge, which PKCE Optional allows, and exchange each code with no verifier. Keep the second access token asOLD_TOKEN.
eval "$(btl-lab state)"
echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CARDS_ID&redirect_uri=$(enc https://beyondthelogin.dev/lab/callback/)&scope=photos.read&state=$STATE"
read -r CODE
curl -s -u "$CARDS_ID:$CARDS_SECRET" -d grant_type=authorization_code -d "code=$CODE" \
--data-urlencode "redirect_uri=https://beyondthelogin.dev/lab/callback/" "$ISSUER/oauth/token" | jq -r .access_token
Walkthrough
Inventory from registrations. In OAuth > Clients, list each lab client's type, grants, response types, PKCE policy, Restrict scopes and redirect URIs, in the shape of the lesson's table.
Inventory from behavior. In Audit, filter for
oauth.authorizeand thenoauth.tokenover the last 30 days.lab-tmp-greeting-cardsshowscode_issuedand successful token requests.lab-tmp-old-uploadershows nothing at all.
Write down what Audit cannot tell you: whether each request carried a PKCE challenge, which redirect URI it used, which response type and grant it used, and how the client authenticated. These are the columns the lesson's table depends on (G52). Audit also keeps 30 days of history, not the lesson's 90.
Why it matters: an inventory comes from what clients actually do, not what their registrations allow. Some of that evidence is not recorded here yet, so part of this inventory is a guess.
Make the destination work before asking anyone to move. The greeting cards developer adds PKCE in their own code first. Run a flow with
eval "$(btl-lab pkce)",code_challenge=$CHALLENGE&code_challenge_method=S256, and exchange with-d "code_verifier=$VERIFIER": a token, while the policy is still Optional.
The downgrade check the server already enforces. Get a code without a challenge, then exchange it with a verifier anyway. The answer is
invalid_grant, "code_verifier does not match the code_challenge sent in the authorization request."
Why it matters: once a client uses PKCE, the server must enforce it completely. A verifier for a code issued without a challenge is refused, so the protection cannot be stripped by removing the challenge.
Enforce. Set
lab-tmp-greeting-cardsto PKCE for confidential clients Required and save. The editor warns that saving revokes the client's tokens, codes and consent:btl-lab introspect "$OLD_TOKEN"answers{"active": false}. Repeat the setup request without a challenge: the callback carrieserror=invalid_request, "This client must send a PKCE code_challenge."
Tighten the return addresses. Remove
/cards/oldand/cards/unusedand save. An authorization request usinghttp://127.0.0.1:8765/cards/oldnow stops on the tenant's error page.
Retire what is unused. The inventory showed
lab-tmp-old-uploaderhas no activity. Untick Enabled and save, confirm again that nothing in Audit used it, then delete it.
Write the lesson's per-client plan for
lab-tmp-greeting-cards, with the dates you would have used for report-only and enforcement, and the evidence you would have wanted before each date.
Client: lab-tmp-greeting-cards
PKCE (S256): report-only from <date>
enforced from <date>
Redirect URIs: three entries, replaced by https://beyondthelogin.dev/lab/callback/ on <date>
Last 7 days: <requests that would have been refused>
Planned walkthrough
These steps need client usage inventory and report-only enforcement (G52).
Open OAuth > Clients > lab-tmp-greeting-cards > Usage: grant types, response types, PKCE presence, redirect URIs actually used, authentication method and last use, over 90 days.
Set PKCE to Report-only. Requests without a challenge still succeed, and Logs counts each one as "would have been refused".
Change the client to send a challenge, watch the count fall to zero, then set PKCE to Enforced. Nothing changes for the client's users.
Break it
Step 6 is the deliberate enforcement. It is the secure end state, not a weakened one.
Check your work
Press Check my progress. Audit also shows the tenant.oauth.clients.update that removed the two redirect URIs, and the one that disabled lab-tmp-old-uploader before it was deleted.
Cleanup
Delete
lab-tmp-greeting-cards.Confirm
lab-tmp-old-uploaderis gone.
Missing infrastructure
G52, client usage inventory and report-only enforcement. Per-client records of grant type, response type, PKCE presence, redirect URI used, authentication method and last use, kept long enough for a 90 day view, and a per-client off, report-only or enforced setting for PKCE, redirect URI lists and grant removal, with "would have been refused" counts in Logs. With them, the planned steps replace the guesswork in step 3.