Auth: Do not treat a boot-time token refresh rejection as a session timeout (closes #23462) - #23494
Conversation
A definitive /token rejection called timeOut() unconditionally, so the boot-time refresh on the post-sign-out landing page (/umbraco/logout) raised a spurious timeout. The app answered with the "session timed out" login modal, which re-authenticates in a popup and leaves navigation to the caller — and the logout route has no auth guard, so a successful sign-in went nowhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @iOvergaard's task in 2m 43s —— View job PR ReviewTarget: Fixes a v17.6 regression where
No Critical or Important issues found. A few observations worth noting: Suggestions
ApprovedThe fix is minimal and precisely targeted, the logic is sound across all code paths (signed-out cold boot, live-session timeout, transient failure), and both tests are well-constructed — the corrected existing test seeds the session it needs, and the new test reproduces the exact regression scenario. No breaking changes. |
There was a problem hiding this comment.
Pull request overview
Adjusts the backoffice auth refresh logic to avoid treating a boot-time refresh-token rejection (after explicit logout, when no session exists) as a session timeout, restoring the expected post-logout login/redirect flow on guard-less routes like /umbraco/logout.
Changes:
- Only call
timeOut()on fatal/tokenrefresh rejections when a session was already established (hadSession). - Updates the existing invalid_grant timeout test to seed a session first (via
sessionUpdatebroadcast). - Adds a new unit test ensuring a boot-time invalid_grant with no session does not trigger timeout and leaves the context unauthorized.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.ts | Gate timeOut() behind a “session existed” check during fatal refresh rejection handling. |
| src/Umbraco.Web.UI.Client/src/packages/core/auth/auth.context.test.ts | Aligns timeout behavior tests with real session establishment and adds coverage for the “no session to lose” boot path. |
|
…imeout (closes #23462) (#23494) fix(auth): only time out a session that actually existed A definitive /token rejection called timeOut() unconditionally, so the boot-time refresh on the post-sign-out landing page (/umbraco/logout) raised a spurious timeout. The app answered with the "session timed out" login modal, which re-authenticates in a popup and leaves navigation to the caller — and the logout route has no auth guard, so a successful sign-in went nowhere. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>



What
Fixes #23462 — a 17.6.0-rc regression where signing out lands you on a "Your session has timed out" screen, and signing in from there never returns you to the backoffice.
Targeting
release/17.6because the regression is only on that line (introduced by #23079, which is in17.6.0-rcbut not in17.5.3), so it would otherwise ship in 17.6.0.Why it happens
Sign-out sends the browser to
/umbraco/logout, a full page load, so the app boots and runs its normal session probe (setInitialState()→/token,grant_type=refresh_token). There is no cookie any more, so OpenIddict answers400 {"error":"invalid_grant"}— and since #23079,#performRefresh()treats any definitive rejection as an expired session and callstimeOut(), even when there was no session in the first place.Both reported symptoms follow from that one call:
timeoutSignalmakesUmbAppAuthControlleropen the login modal intimedOutstate — hence the "Your session has timed out" wording on a deliberate sign-out.timedOutstate deliberately re-authenticates in a popup rather than redirecting, so a timed-out editor does not lose unsaved work, and on success it only closes the modal — navigation is the caller's job. Here the caller is the timeout observer, and thelogoutroute has no auth guard, so nothing navigates. The popup closes and you are left on/umbraco/logout.Two things kept this quiet:
auditTimeontimeoutSignal. Only the guard-less routes (logout,error, and the redirect-flowoauth_complete) are exposed.BackOfficeLogout.spec.tsasserts the landing URL, then re-navigates to/umbracoand asserts the login page — it never signs in from the landing page.The fix
#performRefresh()only times the user out when a session actually existed. A rejected refresh with nothing in hand means nobody is signed in, which the ordinary login redirect already handles. #23079's actual fix (live session, rejected refresh → time out) and the#sessionDeadlatch are untouched.This restores the pre-17.6 behaviour on the sign-out landing page, and it is the same distinction the v19 cookie-auth work (#23484) makes explicitly: a cold boot has nothing to preserve and takes the full-page redirect, while a timed-out session gets the modal and the popup.
Testing
does not time the user out when there was no session to losefails before the fix (expected 1 to equal 0) and passes after.times the user out on a definitive invalid_grant failuretest: it asserted the timeout with no session established, which is exactly why it did not catch this. It now seeds a session via thesessionUpdatebroadcast first, matching the Session timeout modal "Stay logged in" button does not prevent automatic logout #22986 scenario it exists for.Follow-up
main(v18) carries the identical#performRefresh, so it needs this on the merge-up. v19's cookie-auth branch removes the machinery entirely, so nothing to do there.🤖 Generated with Claude Code