OPENID CONNECT · LAB
Watch an ID token expire, separate iat from auth_time, and match nonces exactly
Shorten lab-collage's ID token lifetime, let a real token expire, see single sign-on keep an old auth_time, enforce max_age, and refuse nonces that differ by case or attempt.
ReadyIncludes a simulationUses your lab tenant
The lesson
Builds on: Issuer, audience, and authorized party.
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.
Set lab-collage's ID token lifetime to 60 seconds
Recorded as
tenant.oauth.id_token_managers.updatesucceeded.Receive a 60-second ID token
Recorded as
oauth.tokensucceeded forlab-collageabout[email protected].Set the lifetime to one hour for the iat window
Recorded as
tenant.oauth.id_token_managers.updatesucceeded.max_age makes the provider authenticate Ava again
Recorded as
oauth.authorizesucceeded (user_signed_in) forlab-collageabout[email protected].Set the lifetime back to 300 seconds
Recorded as
tenant.oauth.id_token_managers.updatesucceeded.
Setup
You need
lab-collagewith its ownlab-collageID token manager, Ava, the helpers from the authentication request lab, andEXPECTED_ISSUER,EXPECTED_AUDandID_ALGS. Keepbtl-lab callbackrunning.Define a short alias for this lab's verification, so each step changes only what it is testing:
check() { btl-lab verify "$ID_TOKEN" --issuer "$EXPECTED_ISSUER" --audience "$EXPECTED_AUD" --algs "$ID_ALGS" --type id --nonce "${CHECK_NONCE:-$NONCE}" "$@"; }
Press Start.
Walkthrough
Expiry. In OAuth > ID Token Management, set the
lab-collagemanager's lifetime to 60 seconds. Sign in (signin_url 'openid', thenexchange) and runcheckat once: it ends inACCEPTwith about a minute left. Wait two minutes and runcheckagain. It now stops at the expiry check.
Why it matters: exp is strict. A minute of leeway covers clocks that disagree slightly; it does not rescue a token that expired a minute ago.
The
iatwindow is separate fromexp. Set the lifetime to 3600 seconds, sign in again, wait six minutes, and runcheck --max-iat-age 300. It stops at the issued-at check whileexpis still nearly an hour away.
Why it matters: the relying party decides how old a token may be when it arrives, whatever exp says. The same window bounds how long accepted nonces need remembering.
Read the times as dates:
btl-lab decode "$ID_TOKEN"
date -u -d @<iat>; date -u -d @<exp>; date -u -d @<auth_time>
Single sign-on, the "next morning". Start another sign-in without signing in again. The tenant asks nothing. Decode the new token:
iatis new,auth_timeis still the time of your earlier password sign-in. Fill in the lesson's table with your own values.
Demand a recent sign-in. Wait until your last password sign-in is more than a minute old, then run
check --max-age 60on the token from step 4. It stops at the authentication age check. Now ask the provider instead:signin_url 'openid' '&max_age=60'. The tenant asks for Ava's password. Afterexchange,check --max-age 60ends inACCEPT.
Why it matters: decisions about sensitive actions use auth_time, never iat. A token seconds old can carry an authentication a day old.
Restore: set the lab-collage manager's lifetime back to 300 seconds now.
The nonce table. Using your latest token, run each row of the lesson's table:
CHECK_NONCE="$NONCE" check
CHECK_NONCE="${NONCE^^}" check
CHECK_NONCE="<a nonce from an earlier attempt>" check
unset CHECK_NONCE
Only the first ends in ACCEPT. Then make a request without a nonce: run signin_url 'openid', delete &nonce=... from the URL, sign in and exchange. The token has no nonce claim, and check with your attempt's nonce refuses it.
Why it matters: the nonce is compared exactly, after the signature, against the value stored with the attempt. A missing nonce is a failure, not a skipped check.
Break it
Clock drift.
Simulation. you cannot change the tenant's clock, so you shift your verifier's clock instead. The token stays exactly as the tenant issued it.
Run check --clock-offset -600 on a fresh token: it stops at the issued-at check, because to a clock ten minutes slow the token comes from the future. Run check --clock-offset 900: it stops at the expiry check, as a server running fast would. Without the option the same token passes.
Replay. Keep a
claimedflag for the attempt, as in the complete sign-in lab. Accept a fresh token once and set the flag. Present the same token again: your handler finds the attempt already claimed and refuses before any check runs.
Check your work
Press Check my progress. It looks for, in order: the lifetime change to 60 seconds, a lab-collage token request, the change to 3600 seconds, oauth.authorize with user_signed_in from the max_age=60 request, and the change back to 300 seconds.
In Audit, the single sign-on request in step 4 shows request_started and code_issued with no user_signed_in. Your verifier's expiry, issued-at and nonce refusals appear only in your terminal.
Cleanup
Confirm that the
lab-collageID token manager's lifetime is 300 seconds.Run
unset -f check; unset CHECK_NONCE.