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.
Sign in to start this lab and check your progress. Log in or create an account.
A response type the printer may not use is refused
Recorded as
oauth.authorizerejected (unauthorized_client) forlab-printer.A scope outside the registration is refused
Recorded as
oauth.authorizerejected (invalid_scope) forlab-printer.An unsupported challenge method is refused
Recorded as
oauth.authorizerejected (invalid_pkce) forlab-printer.The printer receives less than it requested
Recorded as
oauth.tokensucceeded forlab-printerabout[email protected].A valid request only starts a decision
Recorded as
oauth.authorizesucceeded (request_started) forlab-printer.
Setup
Set the
lab-printervariables (ISSUER,CLIENT_ID,CLIENT_SECRET,REDIRECT_URI,enc) as in the earlier labs of this section, and press Start on this page.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
Read each line of the output.
response_type=codeasks for a code, not a token in the return URL.client_idis public.redirect_uridecodes to your callback address.scopeis the requested access.stateand 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.
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.
A response type the printer may not use: open
RT=token base. This time the browser goes to your callback witherror=unauthorized_client,stateandiss, because the client and redirect URI were valid.
A scope outside the registration: open
SC="photos.read photos.delete" base. The callback carrieserror=invalid_scope. Then openSC="photos.read profile" base: two scopes, joined by an encoded space, are accepted and the sign-in page appears. Close that tab.
A challenge method the server does not support: open
CM=plain base. The callback carrieserror=invalid_requestwith "code_challenge must be a 43 character S256 value sent with code_challenge_method=S256."
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.
Start a valid request and walk away from it. Run
eval "$(btl-lab state)", openSC="photos.read profile" basein a private window, sign in as Ben, and close the tab at the consent page. In Audit, this request showsoauth.authorizerequest_startedand 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
Repeat a parameter: open the address from
basewith&state=secondappended. The tenant's error page answersinvalid_requestand does not redirect, because it cannot tell whichstatevalue 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
Confirm
lab-printeruses Default access tokens, then delete thelab-tmp-narrowmanager.