Fix Error After Frontend Session Expiry - #4088
Draft
knoppiks wants to merge 1 commit into
Draft
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4088 +/- ##
==========================================
- Coverage 96.57% 96.56% -0.02%
==========================================
Files 442 442
Lines 28207 28207
Branches 660 658 -2
==========================================
- Hits 27241 27238 -3
Misses 465 465
- Partials 501 504 +3 🚀 New features to boost your workflow:
|
knoppiks
force-pushed
the
4082-show-sign-in-page-instead-of-500-after-session-expiry
branch
2 times, most recently
from
August 17, 2026 09:59
e010e57 to
8c98fe6
Compare
knoppiks
force-pushed
the
4082-show-sign-in-page-instead-of-500-after-session-expiry
branch
from
August 19, 2026 18:41
8c98fe6 to
12540df
Compare
after session expiry. Closes: #4082
knoppiks
force-pushed
the
4082-show-sign-in-page-instead-of-500-after-session-expiry
branch
from
August 24, 2026 19:33
12540df to
44da5ba
Compare
knoppiks
marked this pull request as ready for review
August 24, 2026 19:33
knoppiks
marked this pull request as draft
August 27, 2026 13:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes: #4082
When the session expires, the user has to end up on the sign-in page and be returned to the page they came from
afterwards — no matter which kind of request happened to discover the expiry.
Why this cannot be handled on the server alone
Every browser request reaches the frontend's Node server, and
handleAuthorizationinsrc/hooks.server.tssees the session cookie on all of them. The server can therefore always detect an expired session. What it cannot always do is act on it.On a client-side navigation a universal load (
+page.ts) runs in the browser and fetches with a plainfetch(). Afetch()follows redirects transparently, so answering it with a 307 to the sign-in page means the load receives that page's HTML with status 200, callsres.json()on it, and throws aSyntaxError.For those requests the server answers
401instead (RFC 6750, section 3) and the client turns it into a navigation. The server decides, the client acts.Which of the two applies is decided by one predicate,
canRedirectToSignIn:A redirect is only sent where the browser or SvelteKit's client runtime turns it into a navigation by itself: document loads, SvelteKit's own
__data.jsonrequests, anduse:enhanced form submissions.Where expiry is detected
handleAuthorizationsrc/hooks.server.tshandleFetchsrc/hooks.server.tsensureSignedIn/redirectIfSessionExpiredsrc/lib/sign-in-navigation.tsThere is deliberately no client-side check before a request. An expired access token does not imply a 401: reading the session in
handleAuthorizationrenews it first, and only a failed renewal produces one. Whether a renewal would succeed is not something the client can know, so the expiry is not published to it.Which mechanism to use
routes/[type=type]/[id=id]/+page.tsfetchJsonredirect; SvelteKit turns the load's rejection into a navigation{#await}routes/[type=type]/util.tsfetchJsonStreamedgoto, then returns a promise that never settles so the{#await}stays pendingfetchfrom a component event handlerroutes/[type=type]/search-form.sveltefetchJsonStreamed+page.server.ts)routes/__admin/jobs/+page.server.ts__data.jsonrequest is a data request, so the hook redirects ituse:enhance)routes/CodeSystem/[id=id]/$lookup/+page.server.tsapplyActionnavigates+server.tsproxy routeroutes/[type=type]/+server.tsroutes/[type=type]/+page.tswhile renderinghandleFetchredirects, usingevent.urlas the return-to targetTwo rules follow from the table:
fetchdirectly in browser code.fetchJsonandfetchJsonStreamedalso set a non-HTMLAcceptheader, which is what makesacceptsHtmlDocumentclassify the request as a plain fetch. A barefetch(url)sendsAccept: */*, would be classified as a document load, and would receive a redirect it then followsinto HTML.
fetchJsonwherefetchJsonStreamedbelongs fails silently.The
redirectis thrown, nothing navigates, and the{:catch}block renders an error instead.What changed with #4082
?redirect=${event.url}, unencoded, so the query was lostsignInUrl, percent-encodedhandleFetchredirects withevent.urlfetch+if (!res.ok) error(...)+res.json(), repeated per call sitefetchJson/fetchJsonStreamed