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

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.

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 legacy client redeems a code without PKCE

    Recorded as oauth.token succeeded for lab-tmp-greeting-cards.

  2. A verifier for a code issued without PKCE is refused

    Recorded as oauth.token rejected (pkce_failed) for lab-tmp-greeting-cards.

  3. Require PKCE for the legacy client

    Recorded as tenant.oauth.clients.update succeeded.

  4. A request without a challenge is now refused

    Recorded as oauth.authorize rejected (pkce_required) for lab-tmp-greeting-cards.

  5. Delete the unused client

    Recorded as tenant.oauth.clients.delete succeeded.

Setup

  1. Press Start on this page.

  2. 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/old and http://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.

  1. Create a second temporary client from Web application named lab-tmp-old-uploader, with redirect URI http://127.0.0.1:8765/uploader/callback. Do not use it.

  2. Set ISSUER, CARDS_ID and CARDS_SECRET for lab-tmp-greeting-cards, API_ID and API_SECRET for lab-photo-api, and enc.

  3. 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 as OLD_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

  1. 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.

  1. Inventory from behavior. In Audit, filter for oauth.authorize and then oauth.token over the last 30 days. lab-tmp-greeting-cards shows code_issued and successful token requests. lab-tmp-old-uploader shows nothing at all.

  1. 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.

  1. 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.

  1. 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.

  1. Enforce. Set lab-tmp-greeting-cards to 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 carries error=invalid_request, "This client must send a PKCE code_challenge."

  1. Tighten the return addresses. Remove /cards/old and /cards/unused and save. An authorization request using http://127.0.0.1:8765/cards/old now stops on the tenant's error page.

  1. Retire what is unused. The inventory showed lab-tmp-old-uploader has no activity. Untick Enabled and save, confirm again that nothing in Audit used it, then delete it.

  1. 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).

  1. 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.

  2. Set PKCE to Report-only. Requests without a challenge still succeed, and Logs counts each one as "would have been refused".

  3. 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

  1. Delete lab-tmp-greeting-cards.

  2. Confirm lab-tmp-old-uploader is 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.

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