Session management
All domains, identifiers, and tokens in these examples are fictional.
Lantern's timesheets application runs a timer in the browser while a designer works on a project. If designer-42 signs out of Lantern in another tab, timesheets would like to notice within seconds, stop the timer, and show a signed-out screen, without waiting for a message from the provider or for its own session to time out.
Front-channel and back-channel logout both wait for the provider to announce that a session ended. The Session Management specification has the application keep asking instead, from inside the browser, whether anything has changed at the provider.
Watching the provider's session
Session management starts at sign-in. A provider that supports it adds a parameter to the authentication response, beside the code and state:
https://time.lantern.example/signin/callback
?code=demo-code-31
&state=demo-time-4
&session_state=3c9d4e81a7...e5f0.demo-salt-4
The session state value is shortened. session_state represents designer-42's sign-in state at Lantern's provider, as seen by this one application. To timesheets it is opaque: the application stores it and sends it back later, nothing more.
The provider also lists one more address in its discovery document:
"check_session_iframe": "https://idp.lantern.example/check-session"
This is a page meant to be loaded in a hidden frame inside the application's own page, so that a small piece of the provider runs alongside the application in the browser. Every few seconds, the application asks that piece of the provider whether the session state it holds is still current.
How the check works
The timesheets page holds two hidden frames. One loads the provider's check session page. The other is timesheets' own, and it does the asking. It uses the browser's postMessage interface, which lets pages from different origins exchange messages, each naming the origin it expects on the other side. The message is the client ID and the stored session state, separated by a space. A simplified version of the asking frame's script looks like this:
const providerOrigin = 'https://idp.lantern.example';
const message = 'lantern-timesheets ' + sessionState;
setInterval(() => {
providerFrame.postMessage(message, providerOrigin);
}, 5000);
window.addEventListener('message', (event) => {
if (event.origin !== providerOrigin) return; // answers only from the provider
if (event.data === 'changed') checkWithProvider();
});
The provider's frame runs on the provider's own origin, so it can read the provider's browser state: a value kept in a cookie or in web storage that the provider changes whenever someone signs in or out there. It recalculates the session state from the client ID, the origin the message came from, that browser state, and the salt at the end of the value it received, typically as a hash. Then it replies with one word:
| Reply | Meaning |
|---|---|
unchanged | The recalculated value matches. Nothing has changed, and the application keeps polling. |
changed | The values differ. Something happened at the provider, and the application must find out what. |
error | The message was malformed. The application must not answer it with a prompt=none request, which could loop. |
Both frames check origins. The provider's frame answers only messages from origins it expects, and the application's frame accepts replies only from the provider's origin, so another page cannot feed it a false unchanged. The polling itself never leaves the browser. That was the point of the design: knowing about a sign-out within seconds without sending the provider a request every few seconds.
changed is not a sign-out. It means only that the provider's browser state is different. Perhaps designer-42 signed out, perhaps someone else signed in, or perhaps something changed that has nothing to do with this application; the specification warns that some providers report changes to other sessions too. So timesheets asks the provider properly. It sends an authentication request with prompt=none, and designer-42's earlier ID token as id_token_hint. If a new ID token comes back for designer-42, timesheets stores the new session state and carries on. If the provider answers login_required, or returns an ID token for someone else, timesheets treats it as designer-42 signing out: it ends the session, stops the timer, and shows the signed-out screen.
Why it is rarely used now
The whole design rests on one assumption: that the provider's frame, embedded in the application's page, can read the provider's cookies or storage. In Lantern's case it can, for the reason front-channel logout worked there. idp.lantern.example and time.lantern.example are the same site, so the frame is not third-party.
Most relying parties are not in Lantern's position. If the printer used session management with the photo service, the provider's frame would be a cross-site frame inside a page at printer.example. Browsers that block third-party cookies would withhold the photo service's cookie from it. Browsers that partition cookies and storage would give it a separate, empty set, distinct from the one the photo service sees when you visit it directly. Either way, the frame cannot see your session.
The specification describes the result. A provider that keeps its browser state in a cookie may answer changed to every message, and an application that obediently checks again each time loops through silent sign-in requests without end. Those checks fail for the same reason, because a prompt=none request made in a hidden frame is cross-site too, which is why Prompt and account selection called silent checks in hidden frames unreliable. The specification recommends defensive code that detects the situation and tells the person, and leaves the details to each deployment, because browser behavior keeps changing.
Even where it works, session management reaches only browser tabs that are open and running the script. It cannot end a session on a closed laptop or in a mobile app, and the application's server learns nothing unless the page reports back. It also costs a script in every page, a provider frame, and a check every few seconds.
Applications today usually combine other tools. Back-channel logout ends server-side sessions whether or not a browser is open. Idle and absolute timeouts bound how long a missed message matters. When an application's own session ends, it can send the browser to the provider as a full-page redirect, with prompt=none if it wants no interruption; at the top level, the provider's cookies are first-party, so its answer reflects the session it really holds. And a browser application like timesheets can still notice a sign-out quickly by asking its own server whether its session is valid, which back-channel logout keeps up to date.
You will still meet check_session_iframe in provider metadata, and older single-page applications and client libraries still offer session management as an option. Knowing how it works explains why one of them might loop through sign-in requests, or fail to notice a sign-out at all.
Logout completes the life of a sign-in, from the first request to the ways its sessions end. Advanced OIDC begins with Combining claims from multiple sources and a case the printer has not met yet: a claim the photo service cannot vouch for, such as whether you are a student, supplied by someone else entirely.