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

Logout tokens and an administrator-ended session

Plan validating a real logout token when an administrator locks a user. Today, prove an ID token fails the logout token rules, then lock a user and see which tokens die and which sessions never hear.

PlannedUses your lab tenant

The lesson

Builds on: Local logout and provider sessions.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Planned. The core of this lab waits on platform features that are not built yet. The planned walkthrough shows exactly how it will run; Do today is a real exercise you can do now.

Setup

Your tenant has no backchannel_logout_uri, logout tokens, or delivery with retries yet (G17), and no administrator view to end one user's sessions without changing the account (G30). The provider side of "end everything for a leaver" is real today: locking a user.

  1. source ~/btl-oidc.sh and run btl-lab callback before each sign-in.

  2. Once G17 exists: on lab-collage, register a back-channel logout URI on an HTTPS address the tenant can reach, such as a hosted lab receiver (G4) or your own tunnel, with "session required" off.

Planned walkthrough

  1. Sign Ava in to lab-collage and to a second app such as lab-tmp-slideshow.

  2. In Users, lock Ava. The tenant posts logout_token=... to each registered client's back-channel logout URI.

  3. Your receiver validates the token: the signature by kid from the JWKS with an algorithm you accept for ID tokens, typ logout+jwt, iss, aud, iat and exp (about two minutes), sub or sid present, the back-channel logout member in events, no nonce, and a jti it has not seen. It ends every session for that iss and sub and answers 200 with Cache-Control: no-store.

Why it matters: the logout address is reachable by anyone who can reach the app, so only a validated signature makes the message safe to act on.

  1. Make the receiver answer 503 once. Logs shows a failed delivery and a later retry carrying a newly issued token.

  2. Make it answer 400. The tenant records the failure and does not retry.

Why it matters: retries are for failures that look temporary, and a retry minutes later needs a new token because the first one expired.

Do today

  1. Write the logout token rules. The signature, iss, aud and times are checked exactly as for an ID token; these are the rules that keep the two kinds of token apart:

logout_rules() {
  jq -rn --argjson h "$(part "$1" 1)" --argjson p "$(part "$1")" '
    [ (if $h.typ != "logout+jwt" then "typ is \($h.typ // "missing")" else empty end),
      (if ($p.sub == null and $p.sid == null) then "no sub or sid" else empty end),
      (if ($p.events["http://schemas.openid.net/event/backchannel-logout"] | type) != "object" then "no back-channel logout event" else empty end),
      (if $p.nonce != null then "nonce present" else empty end) ]
    | if length == 0 then "accept" else "reject: " + join("; ") end'
}
  1. Feed it a real ID token. Sign Ava in to lab-collage, validate the ID token with btl-lab verify (it passes), then run logout_rules "$ID_TOKEN": reject: typ is JWT; no back-channel logout event; nonce present.

Why it matters: an ID token must never be accepted as a logout token. The nonce rule guarantees it from both sides, because a logout token slipped into a sign-in fails the nonce check too.

  1. Prepare for an administrator ending Ava's access. Sign her in to lab-collage with offline access and keep all three tokens:

SCOPE="openid photos.read offline_access"
signin prompt=consent
redeem '<code>'
AT=$TOKEN RT=$REFRESH
  1. In Users, lock Ava. Then check each piece:

signin prompt=none
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/token" -d grant_type=refresh_token --data-urlencode "refresh_token=$RT" | jq .error
curl -si -H "Authorization: Bearer $AT" "$ISSUER/oidc/userinfo" | grep -i '^www-authenticate'
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/introspect" --data-urlencode "token=$AT" | jq .active
btl-lab verify "$AT" --issuer "$ISSUER" --audience "$ISSUER/resource" --type at+jwt

The silent check returns login_required, the refresh invalid_grant, UserInfo Bearer error="invalid_token", and introspection false. Local verification of the same JWT access token still passes: its signature is good and exp has not passed. Your own app's local session, and any other app's, are untouched.

Why it matters: the provider ended everything it controls. A resource server that validates JWTs locally does not notice, and relying parties' sessions run on until a logout message, which is the missing piece, or their own timeouts.

  1. Unlock Ava in Users. Her old tokens stay dead; she signs in again.

Break it

Planned, once G17 exists: let the tenant's retry deliver a token your receiver already processed, and see it rejected on jti; process one after its two minutes, and see it rejected on exp.

Check your work

Today: Audit shows tenant.users.lock and tenant.users.unlock, then oidc.userinfo and oauth.token rejections for lab-collage. Your notes record which checks still passed after the lock.

Once G17 exists, Logs shows each delivery per client with its outcome, and your receiver's log keeps the jti, the result and the number of sessions ended, never the token.

Cleanup

  1. Make sure Ava is unlocked, and set SCOPE="openid profile email".

  2. Once G17 exists, remove the back-channel logout URI.

Missing infrastructure

  • G17 (OIDC logout). Back-channel logout registration, logout tokens (typ logout+jwt, events, sid or sub, jti, short exp), delivery with bounded retries, and delivery events in Logs. The toolkit's btl-lab verify would also gain a logout token type.

  • G30 (admin session listing and kill-session). Listing and ending a user's sessions from Users without locking the account.

  • G4 (hosted lab receiver). The tenant cannot reach 127.0.0.1, so learners without public HTTPS need a hosted receiver to see deliveries.

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