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

Build the printer's authorization request one parameter at a time

Change one parameter at a time, see which mistakes stop on the tenant's error page and which return to the printer, and see that a valid request starts a decision rather than granting access.

ReadyUses your lab tenant

The lesson

Builds on: Following the complete exchange.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

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. A response type the printer may not use is refused

    Recorded as oauth.authorize rejected (unauthorized_client) for lab-printer.

  2. A scope outside the registration is refused

    Recorded as oauth.authorize rejected (invalid_scope) for lab-printer.

  3. An unsupported challenge method is refused

    Recorded as oauth.authorize rejected (invalid_pkce) for lab-printer.

  4. The printer receives less than it requested

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

  5. A valid request only starts a decision

    Recorded as oauth.authorize succeeded (request_started) for lab-printer.

Setup

  1. Set the lab-printer variables (ISSUER, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, enc) as in the earlier labs of this section, and press Start on this page.

  2. Define the printer's base request. Each parameter can be changed for one call, for example SC="photos.read profile" base.

eval "$(btl-lab pkce)"; eval "$(btl-lab state)"
base() { printf '%s\n' "$ISSUER/oauth/authorize?response_type=${RT-code}&client_id=${CID-$CLIENT_ID}&redirect_uri=$(enc "$REDIRECT_URI")&scope=$(enc "${SC-photos.read}")&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=${CM-S256}"; }
base | tr '?&' '\n\n'   # the lesson's layout: one parameter per line

Walkthrough

  1. Read each line of the output. response_type=code asks for a code, not a token in the return URL. client_id is public. redirect_uri decodes to your callback address. scope is the requested access. state and the S256 challenge are the two protections. Then confirm that neither secret is in the request.

base | grep -c -e "$CLIENT_SECRET" -e "$VERIFIER"   # prints 0

Why it matters: everything in this request travels through the browser. The client secret and the PKCE verifier stay with the printer's backend.

  1. An unknown client: open the address from CID="${CLIENT_ID}x" base. The tenant shows its own error page, and nothing reaches your callback.

Why it matters: until the client and its redirect URI are verified, the server has nowhere trustworthy to send an error, so it answers the browser directly.

  1. A response type the printer may not use: open RT=token base. This time the browser goes to your callback with error=unauthorized_client, state and iss, because the client and redirect URI were valid.

  1. A scope outside the registration: open SC="photos.read photos.delete" base. The callback carries error=invalid_scope. Then open SC="photos.read profile" base: two scopes, joined by an encoded space, are accepted and the sign-in page appears. Close that tab.

  1. A challenge method the server does not support: open CM=plain base. The callback carries error=invalid_request with "code_challenge must be a 43 character S256 value sent with code_challenge_method=S256."

  1. Requesting is not receiving. In Access Token Management, create a temporary manager lab-tmp-narrow (Signed JWT, an active key) with this advanced issuance policy, and save.

return {allow: true, claims: {}, scopes: context.scopes.filter(scope => scope !== 'profile')};

Open OAuth > Clients > lab-printer, set Access token manager to lab-tmp-narrow and save. Run eval "$(btl-lab pkce)"; eval "$(btl-lab state)", open SC="photos.read profile" base, sign in as Ava and approve both scopes. Copy the code and exchange it.

read -r CODE
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=authorization_code -d "code=$CODE" \
  --data-urlencode "redirect_uri=$REDIRECT_URI" -d "code_verifier=$VERIFIER" "$ISSUER/oauth/token" | jq .scope

The response says "photos.read".

Why it matters: the printer chooses what to ask for, and the photo service decides what to issue. A client reads the granted scope instead of assuming its request was met.

Restore: in OAuth > Clients > lab-printer, set Access token manager back to Default access tokens and save.

  1. Start a valid request and walk away from it. Run eval "$(btl-lab state)", open SC="photos.read profile" base in a private window, sign in as Ben, and close the tab at the consent page. In Audit, this request shows oauth.authorize request_started and nothing after it.

Why it matters: receiving a valid request at the authorization endpoint is the beginning of a decision, not evidence that one was made.

Break it

  1. Repeat a parameter: open the address from base with &state=second appended. The tenant's error page answers invalid_request and does not redirect, because it cannot tell which state value to echo back.

Check your work

Press Check my progress. Logs also has oauth.authorize rejected rows for invalid_client (step 2) and invalid_request (Break it). They are error pages with no client attribution, so they do not appear in Audit.

Cleanup

  1. Confirm lab-printer uses Default access tokens, then delete the lab-tmp-narrow manager.

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