Backend / Authentication / SSO / 05_single_logout.md

Single Logout (SLO)

Updated 6 interview angles 7 min read source
On this page14
  1. Three logout scopes
  2. Local logout (the minimum)
  3. OIDC RP-initiated logout (end_session_endpoint)
  4. OIDC front-channel logout
  5. SAML Single Logout
  6. Why SLO is rarely truly “single”
  7. Pragmatic alternatives
  8. Logging out a stolen session
  9. OAuth 2.0 Token Revocation (RFC 7009)
  10. OIDC end_session_endpoint discovery
  11. Implementation checklist
  12. Common pitfalls
  13. Common interview confusions
  14. Interview angle

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)

python
@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:

text
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:

html
<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:

http
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:

text
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”:

  1. Local logout + redirect to IdP end_session_endpoint — covers most users (IdP session ends, future logins require re-auth).
  2. Short access tokens (5–15 min) so revocation propagates quickly via token expiry.
  3. Refresh token revocation on logout — call /revoke so refresh fails next time.
  4. 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:

  1. Revoke their refresh token(s) at the IdP.
  2. Invalidate all server-side sessions for that user (delete by user_id index in Redis).
  3. 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)

http
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:

python
@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:

json
{
  "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_endpoint with id_token_hint.
  • post_logout_redirect_uri registered 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_token JWT (signature, iss, aud, sub or sid).
  • Delete sessions matching sub and/or sid from 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_uri not registered — IdP rejects logout request.
  • Validating logout_token insufficiently — accept signed JWT but skip aud/iss — accept malicious logout claims, DoS the user.
  • No state on 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 6

  • “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_endpoint for?” — RP-initiated IdP logout. App redirects user there with id_token_hint and post_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_hint do 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.