OPENID CONNECT · LAB
Front-channel logout through a hidden frame
Plan a front-channel logout page that the tenant loads in a hidden frame with iss and sid, and today prove that a cross-site frame does not carry the tenant's SameSite=Lax session cookie.
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.
- G17 OIDC logout: RP-initiated, front-channel, back-channel, session management
Setup
Your tenant has no frontchannel_logout_uri registration and no signed-out page with frames yet (G17). The browser behavior that makes front-channel logout unreliable can be shown for real today.
source ~/btl-oidc.sh, sign Ava in to Lab Photos in your lab browser (a normal window, not private, so the frame test in Do today uses the same cookies), and runbtl-lab callbackbefore each request.Once G17 exists: on
lab-collageand on a second app such aslab-tmp-slideshow, register the front-channel logout URIhttp://127.0.0.1:8765/logout/front-channelwith "session required" on. It shares the scheme, host and port of their redirect URIs.
Planned walkthrough
Sign in to both clients through one tenant session, and store
issandsidfrom each ID token in that app's local session record:
part "$ID_TOKEN" | jq '{iss, sub, sid}'
Why it matters: the sid stored at sign-in is what a later logout request is matched against.
Sign out from
lab-collagewith RP-initiated logout (Ask the provider to end its session). The tenant's signed-out page embeds one hidden frame per client.The listener logs
GET /logout/front-channel?iss=...&sid=...once for each client. Your handler checksissexactly, ends the sessions whose storedissandsidmatch, and answers withCache-Control: no-storeandContent-Security-Policy: frame-ancestors $ISSUER.
Why it matters: matching sid limits an ordinary GET to the session the provider named, so a stranger's page cannot sign anyone out.
Run it again and close the tab the moment the signed-out page appears. One client is never told, and the tenant cannot know.
Why it matters: nobody retries a front-channel request, because nobody learns that it failed.
Do today
Read the tenant's own session cookie in the developer tools:
__Host-btl-oauth-sessionwithSecure,HttpOnlyandSameSite=Lax.A silent check at the top level:
signin prompt=none
Open the URL directly in the signed-in window. The listener receives a code.
The same request in a cross-site frame. Write a fresh request into a local page and open the file in the same browser:
printf '<p>Framed silent check</p><iframe src="%s" width="600" height="200"></iframe>\n' "$(signin prompt=none)" > frame.html
The frame shows nothing useful, because tenant pages refuse to be framed (X-Frame-Options: DENY and frame-ancestors 'none'). The request still reached the tenant, though, and Audit shows oauth.authorize rejected login_required for lab-collage although Ava is signed in.
Why it matters: inside a frame on another site the tenant is a third party, so the browser keeps its Lax cookie at home. A front-channel logout page framed by a provider on another site loses its cookies the same way, and only a server-side lookup by iss and sid can still find the session.
Close the loop with the lesson: write down which of your own apps keep their sessions on the server (they could still act on
issandsid) and which keep them only in the browser (they could not).
Break it
Planned, once G17 exists: load your front-channel logout page with a sid that belongs to no session. Nothing matches and nothing ends, and the page still answers 200 with Cache-Control: no-store.
Check your work
Today: the top-level code and the framed login_required rejection for lab-collage in Audit, a few seconds apart, while Ava stayed signed in.
Once G17 exists, both local session records are gone after one sign-out, and the tenant records the logout.
Cleanup
Delete frame.html. Once G17 exists, remove the front-channel logout URIs.
Missing infrastructure
G17 (OIDC logout).
frontchannel_logout_uriandfrontchannel_logout_session_requiredper client,sidin ID tokens, per-session tracking of signed-in clients, and a signed-out page that renders one hidden frame per client. With it, the planned walkthrough runs as written.