sfirke opened a new issue, #45587: URL: https://github.com/apache/superset/issues/45587
### Bug description I found this issue by hand, then wrote it up with Claude AI. I edited and am posting this by hand having read it all. Note that this shouldn't affect properly secured deployments, because they're using server-side sessions as described in https://superset.apache.org/admin-docs/security/securing_superset/#session-management-security-critical That said, this was annoying me in my testing and is a regression from 6.1.0, when it ndidn't happen. I believe this was introduced by #44605. With `AUTH_TYPE = AUTH_OAUTH` and the default cookie-based sessions, clicking the SSO provider button soon after the login page loads sometimes fails with: ``` ERROR:flask_appbuilder.security.views:Error authorizing OAuth access token: mismatching_state: CSRF Warning! State not equal in request and response. ``` The user sees the login page again with an access-denied message. Retrying almost always works. **Cause:** two requests set the session cookie at the same time. 1. `/login/` is a frontend route, so `LocationPathnameLogger` (`src/views/App.tsx`) logs `spa_navigation` when it renders. `loggerMiddleware.ts` debounces that for 1 s, then sends it with `navigator.sendBeacon` to `POST /log/?explode=events`. 2. The user isn't logged in yet, so that POST gets a 302 to `/login/?next=…/log/?explode=events`. Rendering that login page writes to the session (it creates a CSRF token) and returns a new `Set-Cookie: session=…`, built from the cookie the request carried, which has no OAuth state. 3. Meanwhile, the user's click on `/login/<provider>` makes Flask-AppBuilder store `oauth_state` in the session cookie and redirect to the identity provider. 4. If the beacon's redirect response is processed after the `/login/<provider>` response, it overwrites the cookie and `oauth_state` is lost. When the identity provider redirects back to `/oauth-authorized/<provider>`, the state doesn't match. Access log from one failed attempt followed by a successful retry (Azure AD; tokens redacted): ``` 19:59:22 "GET /login/?next=%2Fwelcome%2F" 200 19:59:24 "POST /log/?explode=events" 302 <- debounced spa_navigation beacon, user is anonymous 19:59:24 "GET /login/azure?next=%2Fwelcome%2F" 302 <- user clicks SSO; oauth_state stored in cookie 19:59:24 "GET /login/?next=https://<host>/log/?explode%3Devents" 200 <- beacon redirect; Set-Cookie without oauth_state 19:59:25 ERROR ... mismatching_state: CSRF Warning! State not equal in request and response. 19:59:25 "GET /oauth-authorized/azure?code=<…>&state=<…>" 302 19:59:25 "GET /login/" 200 19:59:27 "POST /log/?explode=events" 302 <- beacon fires before the click this time 19:59:27 "GET /login/?next=https://<host>/log/?explode%3Devents" 200 19:59:29 "GET /login/azure" 302 19:59:30 "GET /oauth-authorized/azure?code=<…>&state=<…>" 302 <- success 19:59:30 "GET /welcome/" 200 ``` **How to reproduce:** 1. Configure OAuth login (seen with Azure AD) and keep the default `SESSION_SERVER_SIDE = False`. 2. Log out, or let the session expire. 3. On `/login/`, click the provider button within about 1–3 s of the page loading. 4. Sometimes you're returned to the login page with access denied, and the server logs `mismatching_state`. It depends on timing, so it doesn't happen every time. Logging out and logging back in hits the same window, and so does any visit where the session has expired (for example, the first visit of the day with a short `PERMANENT_SESSION_LIFETIME`). **Expected behavior:** SSO login succeeds on the first click. No `/log/` request should be sent from the login page, or at least none that can change the session. **Possible fixes:** - Frontend: don't send log events when the bootstrap user isn't logged in, or don't log `spa_navigation` for `/login/`. - Backend: `/log/` should return 401 to anonymous requests instead of redirecting to the login page, which is the step that writes the session cookie. ### Screenshots/recordings _No response_ ### Superset version master / latest-dev ### Python version Not applicable ### Node version Not applicable ### Browser Not applicable ### Additional context **Regression in 7.0:** not reproducible on 6.1.0. Replaying the request sequence against both versions with identical config: on 6.1.0 the beacon's follow-up `GET /login/?next=…/log/…` returns no `Set-Cookie`; on 7.0.0rc2 it does, because #44605 now drains `_flashes` (added by the anonymous `/log/` 302) in the login view, which saves the session. That response is what overwrites `oauth_state`. #44605 fixes a real problem (unbounded cookie growth, #44541), so the fix probably belongs on the logger side: skip beacons for anonymous users, or have `/log/` return 401 instead of flash + redirect. ### Checklist - [x] I have searched Superset docs and Slack and didn't find a solution to my problem. - [x] I have searched the GitHub issue tracker and didn't find a similar bug report. - [x] I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
