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

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.

  1. Redeem lab-collage's sign-in code by hand

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

  2. Use the access token where it belongs

    Recorded as oidc.userinfo succeeded (userinfo_served) for lab-collage.

  3. Obtain a real ID token issued to the printer

    Recorded as oauth.token succeeded for lab-printer about [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

  1. You need lab-collage, lab-printer, Ava, the shell variables from the first lab in this track and the signin_url helper from the authentication request lab.

  2. Keep btl-lab callback running in a second terminal.

  3. Press Start.

Walkthrough

  1. Run signin_url 'openid%20profile%20email', sign in as Ava, and check state and iss. This time send the token request yourself, with -i so 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.

  1. Write down the X-Request-ID. In the tenant portal, open Audit and search for it. The oauth.token record carries the same request ID, the client and the outcome, but no token.

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

  1. 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 $TOKEN
  1. Decoding needs no key and proves nothing. Get a real ID token that the same tenant issued to the printer: run a lab-printer sign-in with scope=openid (as in step 1 of the first lab, using PRINTER_ID and PRINTER_SECRET) and keep its ID token as PRINTER_IDT. Decode it with btl-lab decode "$PRINTER_IDT". It reads just as cleanly as your own token, with the same issuer and the same sub.

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.

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

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

  1. Hand your relying party the wrong real token. Put $PRINTER_IDT where your code expects $ID_TOKEN and repeat step 3. Nothing in the decode flags a problem. Only the verification in step 6 refuses it.

  1. Log the response the wrong way, then fix it. Run jq . <<<"$RESP" | head -3 and notice how easily a debugging line prints a live token. Replace it with the jq '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.

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