Summary
Switching accounts across orgs in the same browser session can lose the original post-login destination and bounce the user to the wrong org/login page after a successful sign-in.
The minimal case does not require two concurrent login flows. A user can:
- sign in to sentry.io as account
A and use org A
- later open an org/page that requires account
B
- get sent to sign in
- sign in as account
B
- get bounced to the wrong org/login page instead of the originally requested org/page
The core bug appears to be:
- the auth-required path stores the destination in session
_next
- the org-login
POST path falls back to that session value unless ?next= is present in the request
- switching from authenticated user
A to authenticated user B causes Django auth to flush() the session
_next is therefore lost on the account switch itself
Once _next is gone, the flow falls back to generic login/implicit org resolution, which can send the user somewhere other than the org/page that originally triggered re-auth.
User Impact
- Account switching feels unreliable even in a single-tab, sequential flow.
- A successful login looks like it failed or looped.
- Users get sent back to the wrong org's login page and see a "sign in with a different account" style warning.
This is separate from the expected "one browser profile has one active Sentry account session" behavior. Re-auth is expected. Redirecting to the wrong org after re-auth is the bug.
Reproduction
- Sign in to sentry.io as account
A.
- Use org
A normally.
- Later, in the same browser session, open an org/page that account
A cannot access but account B can.
- Sentry presents sign-in.
- Sign in as account
B.
Expected
After signing in as account B, the user should return to the org/page that originally triggered re-auth.
Actual
After signing in as account B, Sentry can bounce the user to org A's login/warning page instead. Retrying from the intended org can then succeed, which makes the original sign-in look flaky.
Code Path
- Org views deliberately treat "org exists but the current session cannot access it" as auth-required so the user can re-authenticate as a different account:
src/sentry/web/frontend/base.py:615
src/sentry/web/frontend/base.py:631
- React org pages defer to the normal auth-required handler:
src/sentry/web/frontend/react_page.py:183
- The standard auth-required handler stores the requested destination in session
_next and redirects to login:
src/sentry/web/frontend/base.py:495
src/sentry/web/decorators.py:21
_next is global mutable session state and is cleared/re-written whenever a new login flow is initiated:
src/sentry/utils/auth.py:144
- The org-specific login page already has a partial fix for multi-tab flows by re-reading
next on POST:
src/sentry/web/frontend/auth_organization_login.py:71
- But that
POST path only helps if next is present in the request. The standard auth-required redirect usually does not append ?next=... to the login URL:
src/sentry/web/frontend/base.py:495
- On account switch, Django auth flushes the existing session when the current authenticated user changes:
.venv/lib/python*/site-packages/django/contrib/auth/__init__.py:153
- Sentry's auth wrapper calls Django login during the account switch:
src/sentry/utils/auth.py:294
src/sentry/utils/auth.py:362
- That means
_next written earlier in the flow is dropped when user A becomes user B, unless next was carried in the request itself.
- After login, the flow resolves the final destination from
get_login_redirect(), which consumes session _next:
src/sentry/web/frontend/auth_login.py:460
src/sentry/api/endpoints/auth_login.py:78
src/sentry/utils/auth.py:174
- Immediately after login,
_handle_login() also recomputes active_organization without passing an explicit org slug:
src/sentry/web/frontend/auth_login.py:480
determine_active_organization() falls back to implicit org state when no explicit org slug is provided:
src/sentry/web/frontend/base.py:163
src/sentry/web/frontend/base.py:225
Where Things Go Wrong
The standard auth-required redirect relies on session _next to remember where the user was trying to go.
That is already fragile, but the more specific bug is that account switching destroys that state:
- user
A hits org/page for B
- Sentry stores the original destination in session
_next
- org login
POST reuses _next if ?next= was not present in the URL
- successful login as user
B calls Django login()
- Django sees the session belongs to a different authenticated user and flushes the session
_next is gone
- final redirect falls back to generic login/default org resolution instead of the original requested destination
So the original destination is not just "sometimes wrong"; it is structurally lost on account switch when the login flow depends on session _next instead of request-scoped next.
The remaining variability is in the fallback destination after _next is lost. That fallback depends on host/subdomain and implicit org selection, which is why the user can end up on the wrong org/login page.
Fix Direction
- Always append
next when redirecting to login, especially in the standard auth-required path.
- On login
POST, prefer a request-scoped next from GET or POST over session fallback.
- Avoid recomputing active org from implicit session
activeorg immediately after an account switch unless the requested org is explicitly absent.
- Longer-term, stop relying on a single global
_next slot for login continuation and move to a per-flow state token.
Summary
Switching accounts across orgs in the same browser session can lose the original post-login destination and bounce the user to the wrong org/login page after a successful sign-in.
The minimal case does not require two concurrent login flows. A user can:
Aand use orgABBThe core bug appears to be:
_nextPOSTpath falls back to that session value unless?next=is present in the requestAto authenticated userBcauses Django auth toflush()the session_nextis therefore lost on the account switch itselfOnce
_nextis gone, the flow falls back to generic login/implicit org resolution, which can send the user somewhere other than the org/page that originally triggered re-auth.User Impact
This is separate from the expected "one browser profile has one active Sentry account session" behavior. Re-auth is expected. Redirecting to the wrong org after re-auth is the bug.
Reproduction
A.Anormally.Acannot access but accountBcan.B.Expected
After signing in as account
B, the user should return to the org/page that originally triggered re-auth.Actual
After signing in as account
B, Sentry can bounce the user to orgA's login/warning page instead. Retrying from the intended org can then succeed, which makes the original sign-in look flaky.Code Path
src/sentry/web/frontend/base.py:615src/sentry/web/frontend/base.py:631src/sentry/web/frontend/react_page.py:183_nextand redirects to login:src/sentry/web/frontend/base.py:495src/sentry/web/decorators.py:21_nextis global mutable session state and is cleared/re-written whenever a new login flow is initiated:src/sentry/utils/auth.py:144nextonPOST:src/sentry/web/frontend/auth_organization_login.py:71POSTpath only helps ifnextis present in the request. The standard auth-required redirect usually does not append?next=...to the login URL:src/sentry/web/frontend/base.py:495.venv/lib/python*/site-packages/django/contrib/auth/__init__.py:153src/sentry/utils/auth.py:294src/sentry/utils/auth.py:362_nextwritten earlier in the flow is dropped when userAbecomes userB, unlessnextwas carried in the request itself.get_login_redirect(), which consumes session_next:src/sentry/web/frontend/auth_login.py:460src/sentry/api/endpoints/auth_login.py:78src/sentry/utils/auth.py:174_handle_login()also recomputesactive_organizationwithout passing an explicit org slug:src/sentry/web/frontend/auth_login.py:480determine_active_organization()falls back to implicit org state when no explicit org slug is provided:src/sentry/web/frontend/base.py:163src/sentry/web/frontend/base.py:225Where Things Go Wrong
The standard auth-required redirect relies on session
_nextto remember where the user was trying to go.That is already fragile, but the more specific bug is that account switching destroys that state:
Ahits org/page forB_nextPOSTreuses_nextif?next=was not present in the URLBcalls Djangologin()_nextis goneSo the original destination is not just "sometimes wrong"; it is structurally lost on account switch when the login flow depends on session
_nextinstead of request-scopednext.The remaining variability is in the fallback destination after
_nextis lost. That fallback depends on host/subdomain and implicit org selection, which is why the user can end up on the wrong org/login page.Fix Direction
nextwhen redirecting to login, especially in the standard auth-required path.POST, prefer a request-scopednextfromGETorPOSTover session fallback.activeorgimmediately after an account switch unless the requested org is explicitly absent._nextslot for login continuation and move to a per-flow state token.