OPENID CONNECT · LAB
Run every ID token check in order and keep only the answer
Configure an ID token verifier from your own expectations, watch it run the lesson's checks in order on a real token, make it refuse missing settings, and keep the issuer and subject, not the token.
ReadyUses your lab tenant
The lesson
Builds on: Receiving the ID token.
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.
Receive a fresh ID token for lab-collage
Recorded as
oauth.tokensucceeded forlab-collageabout[email protected].Use the access token only after the ID token is accepted
Recorded as
oidc.userinfosucceeded (userinfo_served) forlab-collage.Obtain a real ID token issued to another client
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_urlandexchangehelpers from the authentication request lab.Write down the relying party's expectations. They come from your configuration, never from a token:
export EXPECTED_ISSUER="$ISSUER" EXPECTED_AUD="$CLIENT_ID" ID_ALGS=RS256
Keep
btl-lab callbackrunning in a second terminal and press Start.
Walkthrough
Fetch the key set and see what it holds:
GET$ISSUER/oauth/jwks
Open in console
GET $ISSUER/oauth/jwks HTTP/1.1
Accept: application/jsonOr in the shell: curl -s "$ISSUER/oauth/jwks" | jq '.keys[] | {kid, kty, alg, use}'. On a default tenant you see an RSA key for ID tokens and an EC key for access tokens.
Why it matters: one key set holds keys for different jobs. The relying party picks a key by kid and uses each key with one algorithm only.
Run a fresh
lab-collagesign-in:signin_url 'openid%20profile%20email', sign in as Ava, checkstateandiss, thenexchange '<code>'. KeepATTEMPT_NONCE=$NONCE. Until every check passes, you do not look up an account, greet Ava or touch the access token.
Run the checks:
btl-lab verify "$ID_TOKEN" --issuer "$EXPECTED_ISSUER" --audience "$EXPECTED_AUD" --algs "$ID_ALGS" --type id --nonce "$ATTEMPT_NONCE"
The verifier prints one line per check and ends in ACCEPT. Match each line to the lesson's numbered list: algorithm, key, signature, issuer, audience and authorized party, time, nonce.
Why it matters: every check runs and nothing is used until all of them pass. Verifying the signature early means every later comparison is made against values the provider actually signed.
Only now use the access token. First confirm that it belongs to this ID token, then call UserInfo with it:
btl-lab verify "$ID_TOKEN" --issuer "$EXPECTED_ISSUER" --audience "$EXPECTED_AUD" --algs "$ID_ALGS" --type id --nonce "$ATTEMPT_NONCE" --access-token "$TOKEN"
curl -s "$ISSUER/oidc/userinfo" -H "Authorization: Bearer $TOKEN" | jq '{sub, name, email}'
The at_hash check passes, which ties this access token to this ID token.
Why it matters: both tokens came from the same code. If the code had been misdelivered, the ID token checks are how you find out, so the access token waits for them.
Compare with the tenant's own view. Open
$ISSUER/token-decoder, sign in, and see the decoder report the same signature verification for its token.
Keep the answer, not the token. From the accepted claims, copy
iss,subandauth_timeinto a session record, then drop the token withunset ID_TOKEN. The pairissandsubidentifies Ava's account.auth_timegoes with the session you are about to create. The nonce is finished, and the ID token's five-minute lifetime does not carry over to the session.
Break it
Each run below uses a real token or a real configuration mistake. Watch where each one stops.
A real ID token for another client. Run a
lab-printersign-in withscope=openid(as in the first lab of this track, withPRINTER_IDandPRINTER_SECRET) and keep its ID token asPRINTER_IDTand its nonce asPRINTER_NONCE. Verify it with the collage expectations and the printer's own nonce, so only the audience differs:
btl-lab verify "$PRINTER_IDT" --issuer "$EXPECTED_ISSUER" --audience "$EXPECTED_AUD" --algs "$ID_ALGS" --type id --nonce "$PRINTER_NONCE"
The signature and issuer pass, then the audience check refuses it.
The access token offered as an ID token:
btl-lab verify "$TOKEN" --issuer "$EXPECTED_ISSUER" --audience "$EXPECTED_AUD" --algs "$ID_ALGS" --type id --nonce "$ATTEMPT_NONCE"stops at the algorithm check, because ES256 is not on your ID token list.
A nonce from another attempt: run step 3 with
--nonce "$PRINTER_NONCE". It stops at the nonce check.
A missing setting. A library must refuse, not skip, when an expected value is absent. Run step 3 with
--audience '', then again with the--nonceoption left out. Both runs must end inREJECT. If a verifier you use in your own code accepts either, it is skipping a check.
Check your work
Press Check my progress. It looks for oauth.token succeeded for lab-collage, then oidc.userinfo with userinfo_served for lab-collage (the access token used after the ID token was accepted), then oauth.token succeeded for lab-printer.
Audit shows a successful token request for every attempt, including the ones your verifier refused in Break it. The provider cannot see a relying party's validation result.
Cleanup
Keep EXPECTED_ISSUER, EXPECTED_AUD and ID_ALGS: the rest of the Validation section uses them. Run unset TOKEN ID_TOKEN PRINTER_IDT RESP.