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 the provider session from the relying party

Plan polling the tenant's check session frame and reacting to changed, and today run the top-level silent check that session management depends on and see why the frame design fails cross-site.

PlannedUses your lab tenant

The lesson

Builds on: Front-channel logout.

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.

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

Your tenant returns no session_state in authorization responses and has no check session frame yet (G17). The step that changed triggers, a silent check with the earlier ID token as a hint, is real today.

  1. source ~/btl-oidc.sh, run btl-lab callback before each request, and sign Ava in to lab-collage in your lab browser. Keep her ID token: AVA_COLLAGE_ID_TOKEN=$ID_TOKEN.

  2. Once G17 exists: enable session management for lab-collage.

Planned walkthrough

  1. Sign in to lab-collage. The listener shows session_state beside code, state and iss. Store it with the session.

  2. Serve a page with two hidden frames: the tenant's check_session_iframe, and your own asking frame, which posts "<client_id> <session_state>" to the tenant's origin every five seconds and accepts replies only from that origin.

const providerOrigin = new URL(issuer).origin;
setInterval(() => providerFrame.postMessage(clientId + ' ' + sessionState, providerOrigin), 5000);
window.addEventListener('message', event => {
  if (event.origin !== providerOrigin) return;
  if (event.data === 'changed') checkWithProvider();
});
  1. Replies are unchanged until you sign out of the tenant in another tab. Then the reply is changed, and your page sends a top-level prompt=none request with id_token_hint.

Why it matters: changed is not a sign-out. It only says the provider's browser state differs, so the page must ask properly.

  1. Serve the same page from a site other than the tenant's. The replies are changed forever, and a client that obeys each one loops.

Do today

  1. Confirm the frame is honestly unavailable.

GET$ISSUER/oidc/check_session Open in console
GET $ISSUER/oidc/check_session

The answer is 501 temporarily_unavailable, and your callbacks carry no session_state.

  1. The check that changed would trigger, done at the top level. With Ava signed in:

signin prompt=none "id_token_hint=$AVA_COLLAGE_ID_TOKEN"

A code comes back, and a new ID token for Ava: carry on.

  1. Sign out of the tenant in another tab and repeat step 2: error=login_required. End your app's session and stop the "timer".

  2. Sign in as Ben in another tab and repeat step 2: error=login_required, and Audit records the reason id_token_hint_mismatch. Without the hint you would get a valid ID token for Ben, which session management treats as a sign-out of Ava.

Why it matters: the hint is what turns "someone is signed in" into "the person this session belongs to is signed in".

  1. Repeat step 2 inside a frame, as the frame design does:

printf '<iframe src="%s" width="600" height="200"></iframe>\n' "$(signin prompt=none "id_token_hint=$AVA_COLLAGE_ID_TOKEN")" > frame.html

Open frame.html as a local file while Ava is signed in. Audit records login_required every time. A client that answered each failure with another silent check would loop forever, so add the guard from the lesson: at most one silent check per page visit, then a signed-out screen that waits for the person.

Why it matters: the frame design depends on third-party cookies. Top-level checks, back-channel messages and your own server-side session are what still work.

Break it

Planned, once G17 exists: remove the event.origin check from the asking frame and post unchanged to it from another of your own local pages. The frame accepts the false answer and keeps a signed-out session alive. Put the origin check back.

Check your work

Today: in Audit, for lab-collage, one code_issued for Ava, then login_required after the sign-out, id_token_hint_mismatch while Ben was signed in, and login_required for every framed attempt.

Once G17 exists, your page's log of unchanged and changed replies, followed by one top-level prompt=none event per changed.

Cleanup

Delete frame.html. Sign Ben out and Ava back in.

Missing infrastructure

  • G17 (OIDC logout). session_state in authorization responses, the check session frame at /oidc/check_session (catalogued today, answering 501), and check_session_iframe in discovery. With it, the planned walkthrough runs as written.

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