8000 8000 Keep shared auth session roots and per-session client states on logout by krishhgg · Pull Request #55136 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Keep shared auth session roots and per-session client states on logout - #55136

Open
krishhgg wants to merge 1 commit into
keycloak:mainfrom
krishhgg:fix-55118-logout-auth-session
Open

krishhgg wants to merge 1 commit into
keycloak:mainfrom
krishhgg:fix-55118-logout-auth-session

Conversation

@krishhgg
@krishhgg krishhgg commented Nov 13, 2026 •
Copy link
Copy Markdown

Closes #55118

For the shared root case, backchannelLogout now removes only its logout tab when the root still holds other tabs, and removes the root through the provider when the logout tab is the last one. I kept provider-level removal for the dedicated root because the JPA provider tracks new roots in transientSessions, and removing the last tab through the JPA adapter would leave a root created in the same transaction in that map and persist it on commit. The root that shares the user session id is still removed by removeUserSessionAndRootAuthSession, so the cleanup added in #54942 is unchanged.

For cross-session reuse, createOrJoinLogoutSession replaces a LOGGING_OUT tab whose LOGOUT_USER_SESSION_ID names another user session with a fresh tab, as the issue suggests. A tab without that note is still reused, because it was never used to log out a user session and holds no client logout states.

The same problem exists on the confirmation path, which the issue does not list. When the browser is logged in to session A and id_token_hint names session B, the logout endpoint joins A's logout tab and initiateBrowserLogout later rebinds it to B. That tab holds this request's post_logout_redirect_uri, state and validated hint, so replacing it would lose them. Instead the new AuthenticationManager.bindLogoutSessionToUserSession removes the logout.state.<client> notes for the clients of both user sessions when the binding changes. Auth notes cannot be listed, so a note for a client that neither user session has a client session for at that moment (for example because the previous session or its client session expired) stays in the tab. It only matters if the new session gets a session for that client while its logout is in progress, for example during an upstream identity provider logout. Scoping the notes to the bound user session id, or tracking them in an index note, would close that gap, but it changes what is stored in the logout session, so I would like a maintainer's view before doing that.

For the hash cookie, bindLogoutRootToBrowser now writes KC_AUTH_SESSION_HASH whenever it writes AUTH_SESSION_ID, so both cookies name the same root. It still writes neither for a recovered root.

The tests are in LogoutSessionReuseTest: one browser test per item, a confirmation-path variant of the cross-session test, and a server-side test of the rebinding. The unfinished logout of the other user session is simulated by recording a client as logged out in its logout tab, which is what backchannelLogoutClientSession does. Against a server built from main, the four browser tests fail (root removed, logout token not delivered in both cross-session variants, stale hash). With this change, ./mvnw -f tests/base/pom.xml test -Dtest=LogoutSessionReuseTest,StaleAuthSessionLogoutTest,RPInitiatedFrontChannelLogoutTest passes.

I used an AI coding assistant (Claude) to help write this change; I reviewed and tested it.

backchannelLogout removes only its own tab when the logout session joined a
root that still holds other tabs, such as an unfinished login in the same
browser.

createOrJoinLogoutSession no longer reuses a logout tab that belongs to
another user session, and binding a logout tab to another user session
removes the client logout states recorded for the previous one, so they
cannot suppress the logout of the new user session's clients.

A new logout root bound to the browser also updates KC_AUTH_SESSION_HASH,
so a login that joins it does not keep reloading in authChecker.js.

Closes keycloak#55118

Signed-off-by: Krish Garg <181051779+krishhgg@users.noreply.github.com>
@krishhgg
krishhgg marked this pull request as ready for review November 13, 2026 00:45
@krishhgg
krishhgg requested a review from a team as a code owner November 13, 2026 00:45
Copilot AI balanced review requested due to automatic review settings November 13, 2026 00:45
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Session rebinding can leave stale markers that skip client logout, while replacement logout tabs can unnecessarily evict unfinished login tabs when the shared root reaches its session limit.

2 open issues
What changed in this PR

Addresses #55118 by refining logout authentication-session handling while preserving the session-takeover cleanup introduced in #54942.

Changes:

  • Preserves unrelated tabs when cleaning up shared logout roots.
  • Replaces cross-session logout tabs and clears known client logout states when rebinding on the confirmation path.
  • Synchronizes the authentication-session hash cookie and adds browser and server-side regression tests.
File Description
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​forms/​LogoutSessionReuseTest.java Adds coverage for shared roots, cross-session logout, rebinding, and hash cookies.
services/​src/​main/​java/​org/​keycloak/​services/​managers/​AuthenticationManager.java Updates logout cleanup, tab reuse, client-state clearing, and cookie synchronization.
services/​src/​main/​java/​org/​keycloak/​protocol/​oidc/​endpoints/​LogoutEndpoint.java Uses the binding helper before browser logout.

Review effort: Balanced

Comment on lines +471 to +472
boolean otherUserSession = userSession != null && prevUserSessionId != null && !prevUserSessionId.equals(userSession.getId());
if (!otherUserSession && (!initiateLogout || client.getId().equals(prevAuthSession.getClient().getId()))) {
public static void bindLogoutSessionToUserSession(KeycloakSession session, AuthenticationSessionModel logoutSession, UserSessionModel userSession) {
String boundUserSessionId = logoutSession.getAuthNote(LOGOUT_USER_SESSION_ID);
if (boundUserSessionId != null && !boundUserSessionId.equals(userSession.getId())) {
Set<String> clientUuids = new HashSet<>(userSession.getAuthenticatedClientSessions().keySet());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Logout authentication session handling deletes shared roots, reuses tabs across sessions, and leaves auth session hash stale

2 participants

0