OPENID CONNECT · LAB
Send the token request yourself and take the response apart
Redeem a sign-in code by hand, read every header and field of the token response, and see that a real ID token issued to another client decodes just as cleanly as yours.
ReadyUses your lab tenant
The lesson
Builds on: The authentication request.
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.
Redeem lab-collage's sign-in code by hand
Recorded as
oauth.tokensucceeded forlab-collageabout[email protected].Use the access token where it belongs
Recorded as
oidc.userinfosucceeded (userinfo_served) forlab-collage.Obtain a real ID token issued to the printer
Recorded as
oauth.tokensucceeded forlab-printerabout[email protected].
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
You need
lab-collage,lab-printer, Ava, the shell variables from the first lab in this track and thesignin_urlhelper from the authentication request lab.Keep
btl-lab callbackrunning in a second terminal.Press Start.
Walkthrough
Run
signin_url 'openid%20profile%20email', sign in as Ava, and checkstateandiss. This time send the token request yourself, with-iso you see the headers:
CODE='<code from the listener>'; COLLAGE_NONCE=$NONCE
FULL=$(curl -si -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/token" -d grant_type=authorization_code --data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=http://127.0.0.1:8765/callback" --data-urlencode "code_verifier=$VERIFIER")
sed -n '1,/^\r$/p' <<<"$FULL"
RESP=$(sed '1,/^\r$/d' <<<"$FULL"); unset FULL
TOKEN=$(jq -r .access_token <<<"$RESP"); ID_TOKEN=$(jq -r .id_token <<<"$RESP")
jq 'del(.access_token, .id_token)' <<<"$RESP"
Expect 200, Cache-Control: no-store, an X-Request-ID, and a body with token_type, expires_in, scope and, because the request asked for openid, id_token.
Why it matters: nothing in the request is specific to OpenID Connect. The code itself remembered that it belonged to a sign-in, together with the nonce and the time Ava authenticated.
Write down the
X-Request-ID. In the tenant portal, open Audit and search for it. Theoauth.tokenrecord carries the same request ID, the client and the outcome, but no token.
Split the compact form by hand:
tr '.' '\n' <<<"$ID_TOKEN"
cut -d. -f3 <<<"$ID_TOKEN" | tr -d '\n' | wc -c
btl-lab decode "$ID_TOKEN"
Three Base64url segments: the header, the claims and the signature. An RS256 signature is about 342 characters. The decode shows iss, sub, aud, exp, iat, auth_time and nonce, with the same shape as the lesson's example.
Why it matters: the signature was computed over the first two segments exactly as they appear. Anything you do to read them does not change what was signed.
Use the access token for its job. It was issued for
openid profile email, so it reads the profile at UserInfo and not photos:
GET$ISSUER/oidc/userinfo
Open in console
GET $ISSUER/oidc/userinfo HTTP/1.1
Authorization: Bearer $TOKENDecoding needs no key and proves nothing. Get a real ID token that the same tenant issued to the printer: run a
lab-printersign-in withscope=openid(as in step 1 of the first lab, usingPRINTER_IDandPRINTER_SECRET) and keep its ID token asPRINTER_IDT. Decode it withbtl-lab decode "$PRINTER_IDT". It reads just as cleanly as your own token, with the same issuer and the samesub.
Why it matters: decoding tells you what a token says, not whether it is genuine or meant for you. Anyone can produce text that decodes to Ava's sub.
Verify instead of reading. Run the full checks on both tokens as
lab-collage:
btl-lab verify "$ID_TOKEN" --issuer "$ISSUER" --audience "$CLIENT_ID" --type id --nonce "$COLLAGE_NONCE"
btl-lab verify "$PRINTER_IDT" --issuer "$ISSUER" --audience "$CLIENT_ID" --type id --nonce "$COLLAGE_NONCE"
Your token ends in ACCEPT. The printer's token passes the signature and issuer checks and stops at the audience.
Why it matters: this token came straight from the token endpoint over TLS, and the specification allows relying on that connection instead of the signature. Verifying it anyway costs little and keeps one validation path for every token.
Keep it on the backend. List where the ID token now exists: one shell variable. Agree on the rules the lesson sets: never in a cookie, a page sent to the browser, or a log line, and never used as the application's own session.
Break it
Hand your relying party the wrong real token. Put
$PRINTER_IDTwhere your code expects$ID_TOKENand repeat step 3. Nothing in the decode flags a problem. Only the verification in step 6 refuses it.
Log the response the wrong way, then fix it. Run
jq . <<<"$RESP" | head -3and notice how easily a debugging line prints a live token. Replace it with thejq 'del(.access_token, .id_token)'form from step 1.
Check your work
Press Check my progress. It looks for oauth.token succeeded for lab-collage and Ava, oidc.userinfo with userinfo_served for lab-collage, and oauth.token succeeded for lab-printer.
The X-Request-ID from step 1 finds the lab-collage token request in Audit. Your verifier's audience refusal is recorded only by you.
Cleanup
Run unset TOKEN ID_TOKEN PRINTER_IDT RESP CODE COLLAGE_NONCE.