Skip to content

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

@dferenc-netwizzy

Description

@dferenc-netwizzy

Preflight checklist

Ory Network Project

No response

Describe the bug

When a consent app calls PUT /admin/oauth2/auth/requests/logout/accept, Hydra responds with a redirect_to URL containing a logout_verifier and returns before the session is actually invalidated. The session is only destroyed when the browser subsequently completes the redirect to /oauth2/sessions/logout?logout_verifier=....
This creates a race condition window between:

  1. The consent app receiving redirect_to and redirecting the browser
  2. The browser completing the logout_verifier round-trip to Hydra

If any concurrent browser activity (e.g. background API requests returning 401) triggers a new /oauth2/auth request during this window, Hydra finds the session still valid and silently re-authenticates the user — completely bypassing the logout.
This can be confirmed in consent/handler.go: AcceptLogoutRequest only marks the flow as accepted and returns the verifier URL. The actual session deletion is deferred to the strategy handling the logout_verifier redirect.

Reproducing the bug

  1. Configure an OAuth2 client with a consent app (URLS_LOGOUT) and skip_logout_consent: true
  2. Have a relying party (e.g. Grafana) that makes background API requests while the user is logged in
  3. Click logout on the RP while background requests are still in-flight
  4. The background requests return 401, triggering an immediate /oauth2/auth redirect from the RP
  5. This races against the browser completing the logout_verifier redirect
  6. Hydra finds the session still active and silently re-authenticates (reason: existing_hydra_session)
  7. The user appears to still be logged in despite clicking logout

Consent app logs showing the race (57ms window):

time: 956412  → GET /auth/logout?logout_challenge=fa5f2371...   (logout challenge received)
time: 956470  → AcceptOAuth2LogoutRequest completed             (accept returned, session NOT yet destroyed)
time: 956527  → GET /auth/login?login_challenge=...             (new login challenge — only 57ms later!)
time: 956534  → authn_silent_login, reason: existing_hydra_session  ← session still alive ❌

Relevant log output

Relevant configuration

# Hydra environment variables (sensitive values redacted)
URLS_SELF_PUBLIC: https://accounts.<redacted>/
URLS_SELF_ISSUER: https://accounts.<redacted>/
URLS_LOGIN: https://accounts.<redacted>/auth/login
URLS_CONSENT: https://accounts.<redacted>/auth/consent
URLS_LOGOUT: https://accounts.<redacted>/auth/logout
URLS_ERROR: https://accounts.<redacted>/auth/error

# OAuth2 client configuration (grafana)
client_id: grafana
grant_types: [authorization_code, refresh_token]
response_types: [code]
scope: openid profile email offline_access
skip_consent: true
skip_logout_consent: true
token_endpoint_auth_method: client_secret_post
frontchannel_logout_uri: https://grafana.<redacted>/dashboard/login/generic_oauth/logout
frontchannel_logout_session_required: true
post_logout_redirect_uris: [https://grafana.<redacted>/dashboard/login]

Version

v25.4.0 #de9baaa9bc1b1865710d4e07e4bd0c4aca599447

On which operating system are you observing this issue?

Linux

In which environment are you deploying?

Docker Compose

Additional Context

Relevant code path consent/handler.go#L902 - AcceptLogoutRequest returns redirect_to without destroying the session. Session destruction happens later in the strategy when the verifier is consumed.


Ideal solution

AcceptLogoutRequest should invalidate the session synchronously and atomically at accept time, before returning redirect_to. The logout_verifier redirect can still be used for front-channel logout notifications to other RPs, but session destruction should not be gated on the browser completing that redirect.
This is consistent with the security principle that logout should be a server-side operation not dependent on browser cooperation.
A less invasive alternative would be a per-client or global configuration flag:

oauth2:
  logout:
    synchronous_session_invalidation: true

Workarounds

None fully reliable. Reducing consent app response latency narrows the window but does not eliminate the race. Related issues #3186 and #3540 describe similar timing problems with back-channel logout, but this issue occurs earlier — before the verifier is even consumed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions