From ed264be714aa77076d640737311b76478c96c35c Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Thu, 23 Jul 2026 22:18:33 +0200 Subject: [PATCH] fix(auth): persist session in localStorage so login survives the language switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The language switch is a full-page navigation to a separate bundle (nl at /, en at /en/); sessionStorage's per-tab semantics dropped the login across it. localStorage is unambiguously shared same-origin and survives the hard navigation. Keeps G1 (naam only, never the BSN). Trade-off: the demo session now survives tab close — a real portal keeps auth in an httpOnly cookie/token. Co-Authored-By: Claude Opus 4.8 --- src/app/auth/application/session.store.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/app/auth/application/session.store.ts b/src/app/auth/application/session.store.ts index 26080d0..47374d3 100644 --- a/src/app/auth/application/session.store.ts +++ b/src/app/auth/application/session.store.ts @@ -11,7 +11,7 @@ const STORAGE_KEY = 'session-v1'; unused after login; only `naam` is shown in the chrome. */ function restore(): Session | null { try { - const raw = sessionStorage.getItem(STORAGE_KEY); + const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const parsed = JSON.parse(raw) as Partial; return typeof parsed?.naam === 'string' ? { bsn: '', naam: parsed.naam } : null; @@ -24,10 +24,12 @@ function restore(): Session | null { * Holds the current session for the whole app. Because it is providedIn:'root' * there is exactly one instance — every component that injects it sees the same * session signal, so logging in is instantly visible everywhere (the guard, the - * header, etc.). The session is mirrored to sessionStorage so a refresh or a - * deep-link to a protected route keeps you logged in; it clears when the tab - * closes. ponytail: sessionStorage, not localStorage — no cross-tab sync, which - * matches a single-session portal. + * header, etc.). The session is mirrored to localStorage so a refresh, a deep-link, + * or the full-page navigation the language switch performs (nl at `/` ⇄ en at `/en/`, + * separate bundles) keeps you logged in. ponytail: localStorage, not sessionStorage — + * sessionStorage's per-tab clearing dropped the login on the cross-bundle language + * switch. Trade-off: the demo session now survives tab close; a real portal keeps auth + * in an httpOnly cookie/token, not web storage. */ @Injectable({ providedIn: 'root' }) export class SessionStore { @@ -41,8 +43,8 @@ export class SessionStore { effect(() => { const s = this._session(); // G1: persist only `naam` — never write the BSN (national ID) to storage. - if (s) sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ naam: s.naam })); - else sessionStorage.removeItem(STORAGE_KEY); + if (s) localStorage.setItem(STORAGE_KEY, JSON.stringify({ naam: s.naam })); + else localStorage.removeItem(STORAGE_KEY); }); }