OAUTH 2.0 · LAB
Build the printer's token request by hand
Write the Basic header yourself, send the form-encoded token request, read every field of the response, and see the checks only the token endpoint can make.
ReadyUses your lab tenant
The lesson
Builds on: Correlating requests and responses.
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.
Exchange the code with a hand-built request
Recorded as
oauth.tokensucceeded forlab-printerabout[email protected].The photo API validates the printer's token
Recorded as
oauth.introspectsucceeded (other_client_token_found) forlab-photo-api.A different redirect_uri at the token endpoint is refused
Recorded as
oauth.tokenrejected (redirect_uri_mismatch) forlab-printer.The same code still works with the right redirect_uri
Recorded as
oauth.tokensucceeded forlab-printer.A token request without redirect_uri is refused
Recorded as
oauth.tokenrejected (invalid_request) forlab-printer.
Setup
Set the
lab-printervariables, plusAPI_IDandAPI_SECRETforlab-photo-api, and press Start on this page.Define a quick connection and get a code for Ava.
connect() { 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=photos.read&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"; }
connect
Open the address, sign in as Ava, approve, check that state and iss match your attempt, and read -r CODE.
Walkthrough
Build the client authentication header the way the lesson shows it.
BASIC=$(printf '%s:%s' "$CLIENT_ID" "$CLIENT_SECRET" | openssl base64 -A)
printf '%s' "$BASIC" | openssl base64 -d -A | cut -d: -f1 # prints the client ID
Why it matters: Base64 is an encoding, not encryption. Anyone who sees the header can read the secret, which is why the request must travel over HTTPS and must never be logged in full.
Send the request with every header visible.
curl -s -i -H "Authorization: Basic $BASIC" -H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode grant_type=authorization_code --data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode "code_verifier=$VERIFIER" "$ISSUER/oauth/token"
The answer is 200 with Content-Type: application/json, Cache-Control: no-store, Pragma: no-cache, and a body with access_token, token_type: "Bearer", expires_in and scope: "photos.read". Copy the access token: read -r TOKEN.
Why it matters: the body is form-encoded, not JSON. redirect_uri repeats the earlier value so the server can compare it; it does not cause another redirect.
Read the response the way the printer must.
token_type: Bearer, so the token goes in anAuthorization: Bearerheader.expires_in: store an expiry time,echo $(( $(date +%s) + <expires_in> )), rather than the relative number.scope: what was actually granted, which can be less than the request.no
refresh_token: this connection did not ask for continued access. The refresh token lab adds one.
Use the result. Start the toolkit's photo API in a second terminal with
btl-lab resource --mode introspect, then make the lesson's API request.
curl -s -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8766/photos
The answer is 200. The resource server introspected the token as lab-photo-api before answering.
Why it matters: the token endpoint's success is not the API's decision. The API still validates the token for its own use and checks the operation.
Break it
A mismatched redirect URI. Run
connect, approve as Ava,read -r CODE, then exchange with a different loopback port than the authorization request used.
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=authorization_code -d "code=$CODE" \
--data-urlencode "redirect_uri=http://127.0.0.1:9999/callback" -d "code_verifier=$VERIFIER" "$ISSUER/oauth/token" | jq
The answer is invalid_grant, "redirect_uri must exactly match the value sent in the authorization request." Loopback ports may vary between authorization requests, but the exchange must repeat the exact value from its own request.
Repeat the exchange with
--data-urlencode "redirect_uri=$REDIRECT_URI":200. The refused attempt did not use up the code.
JSON instead of a form: send
-H "Content-Type: application/json" -d '{"grant_type":"authorization_code"}'with the printer's credentials. The answer is400 invalid_request, "Send a POST with an application/x-www-form-urlencoded body, no query string, and each parameter at most once."
Leave out
redirect_urion a fresh code (connectagain). The answer is400 invalid_request, "The request is missing a required parameter or repeats a parameter."
Check your work
Press Check my progress. Logs also has oauth.token rejected malformed_request with status 400 for the JSON body in Break it, which the tenant refuses before it identifies the client.
Cleanup
Stop the resource server. Nothing in the tenant changed.