-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Preflight checklist
- I could not find a solution in the existing issues, docs, nor discussions.
- I agree to follow this project's Code of Conduct.
- I have read and am following this repository's Contribution Guidelines.
- I have joined the Ory Community Slack.
- I am signed up to the Ory Security Patch Newsletter.
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:
- The consent app receiving
redirect_toand redirecting the browser - The browser completing the
logout_verifierround-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
- Configure an OAuth2 client with a consent app (
URLS_LOGOUT) andskip_logout_consent: true - Have a relying party (e.g. Grafana) that makes background API requests while the user is logged in
- Click logout on the RP while background requests are still in-flight
- The background requests return
401, triggering an immediate/oauth2/authredirect from the RP - This races against the browser completing the
logout_verifierredirect - Hydra finds the session still active and silently re-authenticates (
reason: existing_hydra_session) - 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: trueWorkarounds
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.