Single Logout (SLO)
The flip side of SSO. If you signed in once and got into 5 apps, what happens when you log out? Without SLO, you’ve logged out of one app while still being signed in elsewhere — confusing and a security risk on shared computers.
SLO is harder than SSO because it must reach every app the user was signed into, often without browser cooperation. The specs exist; the reality is uneven.
Three logout scopes
| Scope | What it clears |
|---|---|
| Local logout | just this app’s session — IdP session keeps running |
| IdP logout | end the IdP session — but app sessions stay until they expire |
| Single Logout (SLO) | end IdP session + propagate to all apps |
Most apps implement just local logout. Real SLO needs IdP cooperation and per-app endpoints.
Local logout (the minimum)
@app.post("/logout")
def logout(response: Response, sid: str = Depends(get_sid)):
redis.delete(f"sess:{sid}")
response.delete_cookie("session")
return RedirectResponse("/")
Clears the local session. Doesn’t tell the IdP. Doesn’t tell other apps. User opens app B, redirect to IdP, IdP session still valid → still logged in there.
Acceptable for many apps. Insufficient for “logout from everywhere” requirements.
OIDC RP-initiated logout (end_session_endpoint)
OIDC defines a logout endpoint at the OP. The app redirects the user there:
1. App clear local session
2. App → User 302 → IdP/logout?id_token_hint=<id_token>
&post_logout_redirect_uri=https://app/logged-out
&state=<random>
3. IdP validates id_token_hint, ends IdP session
4. IdP → User (optional) confirmation page
5. IdP → User 302 → https://app/logged-out?state=<random>
Key params:
| Param | Effect |
|---|---|
id_token_hint |
the ID token from the original login (so IdP knows which session to end) |
post_logout_redirect_uri |
where to send the user after — must be registered |
state |
CSRF on the callback |
After this, the IdP session is gone. Other apps still have local sessions until those expire — but the next time they redirect to IdP, the user has to re-authenticate.
This is “session-level” SLO — eliminates the auto-login problem without actively terminating every app’s session.
OIDC front-channel logout
To actively terminate other apps’ sessions, OIDC defines two channels:
Front-channel (browser-mediated)
The IdP returns a page with hidden iframes pointing at each RP’s logout endpoint:
<iframe src="https://app1.example.com/logout?iss=...&sid=..."></iframe>
<iframe src="https://app2.example.com/logout?iss=...&sid=..."></iframe>
<iframe src="https://app3.example.com/logout?iss=...&sid=..."></iframe>
Each iframe load triggers the RP’s logout endpoint with the user’s browser cookies. The RP clears its session.
Issues:
- Requires the browser to load all iframes — if the user closes the tab, partial logout.
- Third-party cookies are restricted (Safari, Firefox by default) — RP cookies may not be sent.
- All-or-nothing visibility — you can’t tell if every app actually logged out.
Back-channel (server-to-server)
The IdP makes a server-to-server POST to each RP’s backchannel_logout_uri:
POST /backchannel-logout HTTP/1.1
Host: app1.example.com
Content-Type: application/x-www-form-urlencoded
logout_token=eyJ...
The logout_token is a JWT identifying the session to terminate. The RP validates the JWT and ends the session for that sub / sid.
Pros:
- No reliance on browser cookies.
- Reliable; IdP can retry on failure.
Cons:
- Each RP needs an endpoint reachable from the IdP.
- More complex to deploy.
For high-security multi-tenant SaaS: implement back-channel logout. For most apps: skip and rely on session expiry.
SAML Single Logout
SAML SLO has the same problems and similar bindings. The flow:
1. App → IdP SAML LogoutRequest (Redirect or POST)
2. IdP validates request, ends IdP session
3. IdP → each SP SAML LogoutRequest to each SP that had a session
(via HTTP-Redirect through user's browser, in sequence)
4. Each SP clears its local session, returns LogoutResponse
5. IdP → original app final LogoutResponse
Each “SP cleanup” hop bounces through the user’s browser. If the user closes the tab mid-sequence, the remaining SPs don’t get the LogoutRequest.
SAML also has a SOAP-bound back-channel SLO, but support is uneven.
Why SLO is rarely truly “single”
The combination of:
- Per-app session lifetimes (sometimes hours/days).
- Browser limitations on third-party cookies.
- Mobile apps with cached tokens (back-channel works; front-channel doesn’t).
- Tabs closed mid-sequence.
means SLO is best-effort, not guaranteed. Most enterprise IdPs implement it; results vary.
Pragmatic alternatives
For “user clicked logout, end everything”:
- Local logout + redirect to IdP
end_session_endpoint— covers most users (IdP session ends, future logins require re-auth). - Short access tokens (5–15 min) so revocation propagates quickly via token expiry.
- Refresh token revocation on logout — call
/revokeso refresh fails next time. - Push-based session invalidation — apps subscribe to a logout event stream (Kafka, Redis pub/sub) and end matching sessions.
For audit/compliance (“the user must be logged out of all apps within 60 seconds”): combine short access tokens + IdP session end + push-based revocation. SLO is a nice-to-have on top.
Logging out a stolen session
If a user reports session theft:
- Revoke their refresh token(s) at the IdP.
- Invalidate all server-side sessions for that user (delete by
user_idindex in Redis). - Force re-auth — short access tokens expire within minutes, can’t refresh, user re-authenticates next time.
This is the “log out everywhere” admin operation. Different from SLO (which is “user clicked logout”).
OAuth 2.0 Token Revocation (RFC 7009)
POST /revoke HTTP/1.1
Authorization: Basic <client_creds>
token=<rt>&token_type_hint=refresh_token
Revokes the refresh token (and optionally the access token, depending on OP). On logout:
@app.post("/logout")
def logout(rt: str = Depends(get_refresh_token_from_cookie)):
requests.post(
f"{IDP}/revoke",
auth=(CLIENT_ID, CLIENT_SECRET),
data={"token": rt, "token_type_hint": "refresh_token"},
)
# also clear local session and redirect to IdP end_session
This stops the refresh chain — the user’s existing access token still works until its (short) expiry, but they can’t get new ones without re-authenticating.
OIDC end_session_endpoint discovery
From the OP’s discovery doc:
{
"end_session_endpoint": "https://idp.example.com/connect/endsession",
"backchannel_logout_supported": true,
"frontchannel_logout_supported": true,
"backchannel_logout_session_supported": true
}
Indicates what the OP supports. Your app checks at startup and configures the logout flow accordingly.
Implementation checklist
For a typical web app supporting SSO:
- Local logout endpoint clears server-side session and cookie.
- Logout redirects to IdP
end_session_endpointwithid_token_hint. -
post_logout_redirect_uriregistered with the IdP. - Refresh token revoked on logout via
/revoke. - Session cookie has
HttpOnly,Secure,SameSite=Lax.
For enterprise SSO:
- Implement back-channel logout endpoint if IdP supports it.
- Validate
logout_tokenJWT (signature, iss, aud, sub or sid). - Delete sessions matching
suband/orsidfrom session store. - Test SLO with the customer’s IdP — behavior varies.
Common pitfalls
- Forgetting to revoke the refresh token — user “logs out,” but stolen RT keeps working for days.
post_logout_redirect_urinot registered — IdP rejects logout request.- Validating
logout_tokeninsufficiently — accept signed JWT but skip aud/iss — accept malicious logout claims, DoS the user. - No
stateon logout callback — CSRF attacker triggers logout for the user. - Relying on front-channel SLO with third-party cookies blocked — silent partial logout.
Common interview confusions
- “Logout in OIDC ends sessions everywhere.” — only if SLO is configured and works. By default, RP-initiated logout ends the IdP session (so future app accesses require re-auth) but doesn’t actively terminate other RP sessions.
- “Front-channel SLO is reliable.” — depends on browser cookie policies and the user not closing the tab. Less reliable than back-channel.
- “SAML SLO is universally supported.” — supported but flaky. Many IdPs implement it; many SPs don’t bother. Real-world SLO is best-effort.
Interview angle
- “What does ‘logout’ mean in an SSO system?” — three levels: local (just this app), IdP (end the IdP session), and SLO (end every app’s session). Most apps implement local + IdP redirect; full SLO is harder.
- “What’s the OIDC
end_session_endpointfor?” — RP-initiated IdP logout. App redirects user there withid_token_hintandpost_logout_redirect_uri. Ends the IdP session, returns user to a designated page. - “Front-channel vs back-channel logout?” — front-channel uses browser iframes/redirects to hit each RP’s logout (requires browser cooperation). Back-channel is server-to-server POST from IdP to each RP’s logout endpoint (more reliable, but each RP must be reachable from IdP).
- “How do you handle ‘log out everywhere’ for a compromised account?” — revoke all refresh tokens at the IdP, invalidate all server-side sessions for that user, rely on short access token lifetime to propagate via expiry.
- “Why is SLO often best-effort?” — third-party cookie restrictions break front-channel; user closes tab mid-sequence; app sessions outlast IdP session; mobile apps with cached tokens. Use short access tokens + revocation for reliable propagation.
- “What does
id_token_hintdo in OIDC logout?” — tells the IdP which user’s session to end. Without it the IdP may show a “select user” page or not know what to log out.