OAUTH 2.0 · LAB
See what the browser carries in the code flow, and what it does not
Watch a code, not a token, come back through the browser, see the API refuse the code, and receive the token only in the printer's direct, authenticated request.
ReadyUses your lab tenant
The lesson
Builds on: Public and confidential clients.
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.
Ava approves and the browser carries back a code
Recorded as
oauth.authorizesucceeded (code_issued) forlab-printerabout[email protected].A code without the printer's credentials is refused
Recorded as
oauth.tokenrejected (unsupported_auth_method) forlab-printer.The printer's backend exchanges the code
Recorded as
oauth.tokensucceeded forlab-printer.The API accepts the access token
Recorded as
oidc.userinfosucceeded (userinfo_served) forlab-printer.A token response through the browser is refused
Recorded as
oauth.authorizerejected (unauthorized_client) 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
The eight labs in this section grow one client, lab-printer, from its first request to a fully checked exchange.
Set the shell variables for
lab-printer(from the Public and confidential clients lab), then press Start on this page.
export ISSUER="https://tenant-<id>.beyondthelogin.dev"
export CLIENT_ID="<lab-printer client ID>"
read -rs CLIENT_SECRET
export REDIRECT_URI="https://beyondthelogin.dev/lab/callback/" # or http://127.0.0.1:8765/callback with btl-lab callback running
enc() { jq -rn --arg v "$1" '$v|@uri'; }
Walkthrough
Build the printer's request. It includes
openidso that the UserInfo endpoint can stand in for the photo API.
eval "$(btl-lab pkce)"; eval "$(btl-lab state)"
echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$(enc "$REDIRECT_URI")&scope=$(enc 'openid photos.read')&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"
Open it, sign in as Ava and approve. The callback shows
code,stateandiss. Copy the code:read -r CODE.
Why it matters: everything that crossed the browser holds a short-lived code. No token was placed in a URL, so none can leak through history, logs or a referrer.
Offer the code to the API as if it were a token.
GET$ISSUER/oidc/userinfo
Open in console
GET $ISSUER/oidc/userinfo HTTP/1.1
Authorization: Bearer $CODEThe answer is 401 with WWW-Authenticate: Bearer error="invalid_token".
Why it matters: the code is an intermediate credential. The API does not accept it.
Try the exchange from somewhere that has the code but not the printer's secret: a second terminal, as if the code had been copied out of the browser.
curl -s -i -d grant_type=authorization_code -d "code=$CODE" --data-urlencode "redirect_uri=$REDIRECT_URI" \
-d "code_verifier=$VERIFIER" -d "client_id=$CLIENT_ID" "$ISSUER/oauth/token"
The answer is 401 invalid_client. The code is still unused.
Exchange it as the printer's backend, with its credentials and its PKCE verifier.
curl -s -i -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"
The answer is 200 with Cache-Control: no-store and a JSON body holding access_token, token_type, expires_in, scope and id_token. Copy the access token: read -r TOKEN.
Why it matters: the token arrives in the body of a direct response. That request is where the server could check the printer's secret and its PKCE verifier, two checks a token sent back through the browser would allow neither of.
Call the API with the token: repeat step 3 with
$TOKENinstead of$CODE. The answer is200with Ava'ssub.
Search your browser history for the callback address. The entry holds the code; no access token appears anywhere in history.
Break it
Ask for the token to come straight back through the browser instead.
echo "$ISSUER/oauth/authorize?response_type=token&client_id=$CLIENT_ID&redirect_uri=$(enc "$REDIRECT_URI")&scope=photos.read&state=$STATE"
The callback carries error=unauthorized_client, state and iss. Neither the tenant's flow policy nor the printer's registration allows a token response, which is the safe default the lesson argues for.
Check your work
Press Check my progress. Logs also has a summary row for oidc.userinfo rejected invalid_token with status 401: the code offered as a token.
Cleanup
None. Keep the shell variables for the next lab.