-
-
Notifications
You must be signed in to change notification settings - Fork 169
Description
Summary
OIDC login can leave behind an unbounded number of sqlpage_oidc_state_<csrf> cookies. When a browser triggers several unauthenticated protected requests before the login flow completes, or when the OIDC callback fails and retries, SQLPage creates a new state cookie for each redirect attempt but only removes the single cookie referenced by the successful callback. The remaining state cookies continue to be sent on every request until they expire, which can push the request header over reverse-proxy limits and break authentication.
This matches production failures such as:
client sent too long header line: "Cookie: sqlpage_auth=...; sqlpage_oidc_nonce=...; sqlpage_oidc_state_Yr7ZUpz_4bMFLblLH66vyg=...; sqlpage_oidc_state_MhUHU_XlZyBXb2UVWxSTIQ=...; sqlpage_oidc_state_pEOeNNonvws5gwVdpynZOA=..."
Impact
- Users can get stuck in an authentication failure state because nginx rejects the request before SQLPage sees it.
- The problem is amplified by large ID tokens because
sqlpage_authalready consumes several KB. - A single page load can trigger multiple protected XHR/AJAX requests, so cookie growth can happen quickly.
- Logout does not clean up leftover
sqlpage_oidc_state_*cookies, so stale cookies can survive until expiry.
Root Cause
The current OIDC flow creates a distinct temporary state cookie for every redirect attempt:
handle_unauthenticated_requestalways callsbuild_auth_provider_redirect_responsefor protected unauthenticated requests.src/webserver/oidc.rs:456build_auth_provider_redirect_responsealways creates a freshsqlpage_oidc_state_<csrf>cookie.src/webserver/oidc.rs:737- On success,
process_oidc_callbackremoves only the cookie matchingparams.state.src/webserver/oidc.rs:670andsrc/webserver/oidc.rs:691 - On callback error,
handle_oidc_callback_errorimmediately creates yet another state cookie and does not remove the failed one first.src/webserver/oidc.rs:475 - On logout, only
sqlpage_authandsqlpage_oidc_nonceare cleared; temporarysqlpage_oidc_state_*cookies are not.src/webserver/oidc.rs:536
This is likely a regression from a451c7e7, which switched from a single shared state cookie to one cookie per CSRF token. That fixed concurrent-login overwrites, but it also made cleanup incomplete.
Reproduction
- Configure OIDC protection on a page that triggers multiple protected subrequests or AJAX calls while unauthenticated.
- Open the page in a fresh session so several requests race into
handle_unauthenticated_requestbefore the browser finishes the redirect. - Complete a single successful login.
- Inspect browser cookies or the incoming
Cookieheader.
Actual Behavior
- Several
sqlpage_oidc_state_<random>cookies remain after login. - Future requests include all of them, plus
sqlpage_authandsqlpage_oidc_nonce. - Repeated login retries or failed callbacks add more cookies.
- Eventually nginx rejects requests with
client sent too long header line.
Expected Behavior
Temporary OIDC state cookies should be bounded and aggressively cleaned up. After a successful login or logout, the browser should not keep unrelated stale sqlpage_oidc_state_* cookies.
Suggested Fix Direction
One of these approaches should work:
- Before setting a new
sqlpage_oidc_state_*cookie, clear all existing cookies with thesqlpage_oidc_state_prefix. - On successful callback and logout, iterate over request cookies and remove every
sqlpage_oidc_state_*cookie, not just the matching one. - On callback error, remove the failed state cookie before issuing a new redirect.
- Add a regression test covering multiple concurrent unauthenticated requests and verifying that only bounded temporary cookies remain after login/logout.
Notes
The current LOGIN_FLOW_STATE_COOKIE_EXPIRATION is only 10 minutes, but that is still long enough to break users behind strict proxy header limits, especially with large OIDC ID tokens.