Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

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.

  1. Set lab-collage's ID token lifetime to 60 seconds

    Recorded as tenant.oauth.id_token_managers.update succeeded.

  2. Receive a 60-second ID token

    Recorded as oauth.token succeeded for lab-collage about [email protected].

  3. Set the lifetime to one hour for the iat window

    Recorded as tenant.oauth.id_token_managers.update succeeded.

  4. max_age makes the provider authenticate Ava again

    Recorded as oauth.authorize succeeded (user_signed_in) for lab-collage about [email protected].

  5. Set the lifetime back to 300 seconds

    Recorded as tenant.oauth.id_token_managers.update succeeded.

Setup

  1. You need lab-collage with its own lab-collage ID token manager, Ava, the helpers from the authentication request lab, and EXPECTED_ISSUER, EXPECTED_AUD and ID_ALGS. Keep btl-lab callback running.

  2. 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}" "$@"; }
  1. Press Start.

Walkthrough

  1. Expiry. In OAuth > ID Token Management, set the lab-collage manager's lifetime to 60 seconds. Sign in (signin_url 'openid', then exchange) and run check at once: it ends in ACCEPT with about a minute left. Wait two minutes and run check again. 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.

  1. The iat window is separate from exp. Set the lifetime to 3600 seconds, sign in again, wait six minutes, and run check --max-iat-age 300. It stops at the issued-at check while exp is 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.

  1. Read the times as dates:

btl-lab decode "$ID_TOKEN"
date -u -d @<iat>; date -u -d @<exp>; date -u -d @<auth_time>
  1. Single sign-on, the "next morning". Start another sign-in without signing in again. The tenant asks nothing. Decode the new token: iat is new, auth_time is still the time of your earlier password sign-in. Fill in the lesson's table with your own values.

  1. Demand a recent sign-in. Wait until your last password sign-in is more than a minute old, then run check --max-age 60 on 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. After exchange, check --max-age 60 ends in ACCEPT.

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.

  1. 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

  1. 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.

  1. Replay. Keep a claimed flag 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

  1. Confirm that the lab-collage ID token manager's lifetime is 300 seconds.

  2. Run unset -f check; unset CHECK_NONCE.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab