The Forgotten Side of SSO: Keeping Sessions in Sync
A support ticket from a real category of incident:
A support ticket from a real category of incident:
I was writing a long incident report in the admin console. I had the analytics dashboard open in another tab from this morning and hadn't touched it in an hour. The dashboard timed out, and my admin console tab logged out too. I lost about forty minutes of writing.
Nobody's code was broken. Every component did exactly what it was configured to do. The analytics app enforced its idle timeout, correctly. It initiated single logout, correctly. The identity provider ended the authentication state, correctly. It notified the other relying parties, correctly. The admin console destroyed its session, correctly.
And the user lost forty minutes of work, because an application they weren't using decided on their behalf that they were idle.
This is the part of SSO that nobody designs. Everyone builds single sign-on. Far fewer people think carefully about what happens when several applications each maintain their own view of whether the user is still there — and then act on it.
Single sign-on made sessions plural
Before SSO, session management was simple because it was local. Your app had a session. It timed out. The user logged back in. There was exactly one opinion about the user's state and exactly one system holding it.
SSO didn't replace that. It added a layer above it. Now there's an authentication state at the IdP, plus one session per application, and each application enforces its own lifetime policy.
flowchart TD
IdP["IdP authentication state\n12h absolute"]
IdP --> A["Admin console\n8h idle timeout\n(user is active here)"]
IdP --> B["Analytics\n15m idle timeout\n(idle for 1 hour)"]
IdP --> C["Support tool\n30m idle timeout\n(idle since morning)"]
Three applications, three different timeout policies, one user. That's not a misconfiguration — the analytics tool holds sensitive aggregate data and its 15-minute timeout was a deliberate, defensible security choice. The admin console's 8-hour window is also defensible; people work in it for hours at a stretch.
The problem is that neither team knew about the other's policy, and single logout means the shortest one wins for everybody.
The four ways this goes wrong
Idle in one app triggers logout everywhere. The scenario above. The tightest timeout in your entire application suite effectively becomes the global timeout, because whichever app times out first initiates SLO and takes down the rest. Teams discover this only after a user loses work, and the immediate fix — "turn off single logout" — trades a usability problem for a security one.
The reverse: activity in one app keeps a dead session alive. Some implementations refresh the IdP authentication state on every authorization request. A background tab polling an API every 60 seconds silently extends the session forever. The user walks away from an unlocked laptop; the compliance team's 15-minute idle policy has been quietly defeated by a dashboard doing auto-refresh. This is worse than the first failure, because nothing appears wrong.
Partial logout, which is the most dangerous state. Front-channel logout works via hidden iframes in the user's browser. If a tab is closed, an iframe is blocked by third-party cookie restrictions, or one app is slow to respond, some sessions die and others survive. The user sees a logout confirmation page and reasonably concludes they're logged out everywhere. They're not. On a shared machine, this is a real security incident with a confirmation screen that lied about it.
Silent re-authentication that undoes the logout. The user logs out of App A. App A's session is destroyed, but the IdP authentication state is still alive because App A only did a local logout. App B, which polls in the background, hits its token refresh, gets a fresh token instantly with no prompt, and carries on. From the user's perspective they logged out and one application ignored it.
Why the "obvious" fixes don't work
"Just make all the timeouts the same." This works until the first enterprise customer whose compliance framework mandates 15 minutes for anything touching financial data, while their operations team refuses to re-authenticate hourly in a tool they live in all day. Different applications genuinely have different risk profiles. Forcing a uniform policy means either over-restricting the low-risk apps or under-protecting the high-risk ones.
"Just disable single logout." Now a user who logs out of the admin console is still logged into the support tool with a customer's data on screen. On a shared terminal, that's the failure that ends up in an incident report.
"Just warn the user before timing out." Helpful, and necessary, but insufficient. The warning fires in a background tab the user isn't looking at. They see it thirty seconds after the countdown ended.
Each of these is a real mitigation and none is a solution, because the actual problem is architectural: each application is making an independent decision about a shared piece of state, using only its own local information.
What actually works
Separate "idle" from "logged out"
The critical realization is that idle timeout and session termination are different events that got conflated because, in a single-app world, they were the same thing.
An application being idle means this application should lock, blur its content, and require a re-check before showing data again. It does not mean the user's authentication is over. Locking the analytics dashboard behind a re-auth prompt satisfies the security requirement completely — the sensitive data is no longer visible — without touching anyone else's session.
Reserve actual single logout for genuine termination events: the user explicitly clicking log out, an admin revoking access, or the absolute session lifetime being reached.
flowchart LR
Idle["App idle timeout"] --> Lock["Lock this app only\n(re-auth to resume)"]
Explicit["User clicks Log Out"] --> SLO["Full single logout"]
Absolute["Absolute lifetime reached"] --> SLO
Revoke["Admin revokes"] --> SLO
This single distinction eliminates the majority of lost-work incidents, and it costs you nothing in security posture — arguably it improves it, because now the idle policy is enforced per-app at the correct strictness rather than being globally flattened to whichever app is loosest.
Make activity signals global, not local
If your applications share an SSO session, they should share a notion of activity. A user actively typing in the admin console is not idle from the identity provider's perspective, even if the analytics tab hasn't been touched in an hour.
The practical mechanism is a lightweight, throttled activity ping from each application to the IdP session — not on every keystroke, but once every few minutes when there's genuine interaction. The IdP then knows the user is present, and the "user is idle" decision is made once, globally, with full information, instead of independently by each app with partial information.
The essential detail: this must be real user interaction, not background API traffic. A polling dashboard is not a present user, and treating its requests as activity is exactly how the second failure mode above happens.
Absolute and idle lifetimes are different controls
Idle lifetime asks "has the user been interacting?" Absolute lifetime asks "how long since they proved who they are?" — and it should not be extendable by activity at all.
A user who's been continuously active for fourteen hours should still be asked to re-authenticate, because the risk being managed there isn't inactivity; it's the age of the authentication event. Conflating these means an always-active session never re-verifies, which quietly defeats the point of having a session lifetime.
Save work before you destroy a session
This one is unglamorous and prevents the actual damage in the ticket at the top.
Applications with meaningful user input should draft-save continuously, warn before session-driven navigation, and — most importantly — treat a logout notification as an opportunity to persist state, not just a signal to redirect. When a back-channel logout arrives, the client can flush pending changes to local storage before tearing down.
The user still gets logged out. They just don't lose forty minutes of writing, and the re-authentication returns them to their draft. That's the difference between an annoyance and an angry escalation.
Confirm what actually happened
If logout is partial, say so. An honest "You've been signed out of 3 of 4 applications; Support Tool did not respond" is far better than a green checkmark that isn't true. Users on shared machines can act on accurate information — they can't act on a reassuring lie.
This means tracking logout acknowledgements rather than firing notifications and assuming success. Back-channel logout gives you a real response to check; front-channel largely doesn't, which is another argument for preferring back-channel where the relying parties support it.
What to decide before you have this problem
Concretely, before the first enterprise customer files the ticket:
Write down the timeout policy for every application in the suite, in one table. The moment they're visible together, the mismatches become obvious and the "shortest one wins" dynamic becomes a conscious decision instead of an emergent one.
Decide, explicitly, what each termination event does. Idle in one app, explicit logout, absolute expiry, admin revocation, password change, MFA reset — each should have a documented blast radius. Most teams have never enumerated these, which is why the behavior is inconsistent.
Decide whether activity in one app extends the SSO session. Both answers are defensible. Only one of them should be happening, and it should be on purpose.
Test the multi-app matrix, not the single-app flow. Two apps, one active and one idle. Two apps, log out of one. One app, IdP session expires. Every one of these has a correct behavior and every one of them is a bug your users will find first.
Why this gets missed
Session synchronization falls in the gap between teams. The identity team owns the IdP session and considers per-app timeouts an application concern. Each application team owns its own session and considers cross-app behavior an identity concern. The failure only manifests when a real user has several apps open at once, which is exactly the scenario nobody's integration tests cover, because integration tests test one application.
So it ships. And it works fine, for months, until someone loses forty minutes of work in a tool they were actively using, because a dashboard they'd forgotten about decided they'd gone home.
SSO's promise was that logging in once would work everywhere. The unstated corollary is that everything else about that session now also works everywhere — including the parts you'd rather it didn't. Getting single sign-on right is mostly a protocol problem. Getting session synchronization right is a coordination problem, and coordination problems don't get solved by any individual team doing their own part correctly.