Skip to content

fix: invalidate session synchronously in AcceptLogoutRequest (closes #4070)#4114

Open
scythe-327 wants to merge 1 commit into
ory:masterfrom
scythe-327:fix/synchronous-session-invalidation-4070
Open

fix: invalidate session synchronously in AcceptLogoutRequest (closes #4070)#4114
scythe-327 wants to merge 1 commit into
ory:masterfrom
scythe-327:fix/synchronous-session-invalidation-4070

Conversation

@scythe-327

@scythe-327 scythe-327 commented Jul 15, 2026

Copy link
Copy Markdown

Summary

When a consent app calls \PUT /admin/oauth2/auth/requests/logout/accept, Hydra previously returned the
edirect_to\ URL before the session was actually deleted. The session was only destroyed later when the browser completed the \logout_verifier\ round-trip.

This created a 57ms+ race window where background API requests could trigger silent re-authentication, completely bypassing logout.

Changes

  • In \�cceptOAuth2LogoutRequest\ (\consent/handler.go): decode the challenge to extract the \SessionID, then call \LoginManager().DeleteLoginSession\ synchronously before returning the redirect URL
  • Added \sqlcon\ import for \ErrNoRows\ handling
  • When \completeLogout\ later runs, it finds the session already deleted and short-circuits with a safe redirect

How it fixes the race

\
Before (race window exists):
AcceptLogoutRequest -> returns redirect_to -> [RACE WINDOW] -> completeLogout -> deleteSession

After (race closed):
AcceptLogoutRequest -> deleteSession -> returns redirect_to -> completeLogout -> session already gone -> safe redirect
\\

Testing

The existing test \should return to default post logout because session was revoked in browser context\ in \strategy_logout_test.go\ validates this scenario — it verifies \Skip=false\ on re-login (proving session was deleted) and that Kratos session was disabled.

Closes #4070

Summary by CodeRabbit

  • Bug Fixes
    • Login sessions are now removed immediately when an OAuth2 logout request is accepted.
    • Logout redirects continue successfully when the session has already been removed.
    • Improved handling and logging of session deletion failures.

When a consent app calls PUT /admin/oauth2/auth/requests/logout/accept,
Hydra previously returned the redirect_to URL before the session was
actually deleted. The session was only destroyed later when the browser
completed the logout_verifier round-trip. This created a race window
where background API requests could trigger silent re-authentication,
completely bypassing logout.

This fix deletes the login session synchronously in acceptOAuth2LogoutRequest,
before returning the redirect URL. When completeLogout later runs, it finds
the session already deleted and short-circuits with a safe redirect.

Closes ory#4070
@scythe-327 scythe-327 requested review from a team and aeneasr as code owners July 15, 2026 14:47
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

OAuth2 logout acceptance now retrieves and deletes the associated login session synchronously, tolerates missing sessions, logs other deletion failures, and constructs the logout redirect using the request context.

Changes

OAuth2 logout handling

Layer / File(s) Summary
Synchronous logout session invalidation
consent/handler.go
acceptOAuth2LogoutRequest fetches the accepted logout request, deletes its login session before returning, handles sqlcon.ErrNoRows, logs other deletion failures with the session ID, and builds the redirect with PublicURL(ctx).

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: aeneasr, alnr

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: synchronous session invalidation during AcceptLogoutRequest.
Description check ✅ Passed The description covers the bug, fix, testing, and issue link; only checklist boxes and optional comments are unfilled.
Linked Issues check ✅ Passed The change synchronously deletes the login session before returning redirect_to, matching #4070's required race fix.
Out of Scope Changes check ✅ Passed The diff appears focused on the logout-session fix, with no unrelated functional changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@consent/handler.go`:
- Around line 925-937: Remove the synchronous DeleteLoginSession call from the
logout-accept flow; preserve the verifier-associated session state so
completeLogout can validate it, notify all configured front/back-channel Relying
Parties, and then perform final deletion. Implement an invalidation or
equivalent verifier-scoped state mechanism around completeLogout, ensuring
concurrent authentication cannot reuse the session without causing ErrNoRows to
short-circuit SLO processing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bdbffe00-196d-4a74-a9da-6871a10483a5

📥 Commits

Reviewing files that changed from the base of the PR and between 6b3ddb4 and 2ebb9c7.

📒 Files selected for processing (1)
  • consent/handler.go

Comment thread consent/handler.go
Comment on lines +925 to +937
// Invalidate the session synchronously before returning the redirect URL.
// This closes the race window between AcceptLogoutRequest returning and
// the browser completing the logout_verifier round-trip, during which
// background API requests could trigger silent re-authentication (see #4070).
//
// When completeLogout later runs it will find the session already deleted
// and short-circuit with a safe redirect — no front/back-channel
// notifications are sent, which is acceptable because the session is gone.
if err := h.r.LoginManager().DeleteLoginSession(ctx, logoutRequest.SessionID); err != nil && !errors.Is(err, sqlcon.ErrNoRows()) {
h.r.Logger().WithError(err).WithField("sid", logoutRequest.SessionID).Error("Failed to delete login session during logout accept")
h.r.Writer().WriteError(w, r, err)
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Deleting the session here breaks OIDC Single Logout (SLO) for Relying Parties.

The inline comment states that skipping front/back-channel notifications is "acceptable because the session is gone." However, the IdP's session being gone is exactly why the IdP must notify the Relying Parties—so they can terminate their local sessions.

By deleting the session here, completeLogout will short-circuit. As shown in the codebase snippets, completeLogout relies on the success of s.deleteSession to detect if a verifier is valid and unused. If it receives ErrNoRows (because the session was already deleted here), it immediately returns a redirect and skips processing the front/back-channel logout URLs. This completely breaks OIDC Single Logout, leaving the user actively logged in at all other applications.

To resolve the race condition (#4070) without breaking SLO, you must preserve the state required for the verifier flow. Consider alternative approaches such as:

  1. Marking the session as invalidated (e.g., via a new invalidated_at column) to prevent its reuse during concurrent authentication requests, while allowing completeLogout to read the associated clients and perform the actual hard deletion.
  2. Extracting the front/back-channel clients during this accept phase and persisting them in a short-lived state tied to the verifier, so completeLogout doesn't depend on the login session table to notify RPs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@consent/handler.go` around lines 925 - 937, Remove the synchronous
DeleteLoginSession call from the logout-accept flow; preserve the
verifier-associated session state so completeLogout can validate it, notify all
configured front/back-channel Relying Parties, and then perform final deletion.
Implement an invalidation or equivalent verifier-scoped state mechanism around
completeLogout, ensuring concurrent authentication cannot reuse the session
without causing ErrNoRows to short-circuit SLO processing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: AcceptOAuth2LogoutRequest returns before session is invalidated, causing silent re-authentication race condition

2 participants