fix(auth): persist session in localStorage so login survives the language switch
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 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ const STORAGE_KEY = 'session-v1';
|
|||||||
unused after login; only `naam` is shown in the chrome. */
|
unused after login; only `naam` is shown in the chrome. */
|
||||||
function restore(): Session | null {
|
function restore(): Session | null {
|
||||||
try {
|
try {
|
||||||
const raw = sessionStorage.getItem(STORAGE_KEY);
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
const parsed = JSON.parse(raw) as Partial<Session>;
|
const parsed = JSON.parse(raw) as Partial<Session>;
|
||||||
return typeof parsed?.naam === 'string' ? { bsn: '', naam: parsed.naam } : null;
|
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'
|
* 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
|
* 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
|
* 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
|
* header, etc.). The session is mirrored to localStorage so a refresh, a deep-link,
|
||||||
* deep-link to a protected route keeps you logged in; it clears when the tab
|
* or the full-page navigation the language switch performs (nl at `/` ⇄ en at `/en/`,
|
||||||
* closes. ponytail: sessionStorage, not localStorage — no cross-tab sync, which
|
* separate bundles) keeps you logged in. ponytail: localStorage, not sessionStorage —
|
||||||
* matches a single-session portal.
|
* 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' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SessionStore {
|
export class SessionStore {
|
||||||
@@ -41,8 +43,8 @@ export class SessionStore {
|
|||||||
effect(() => {
|
effect(() => {
|
||||||
const s = this._session();
|
const s = this._session();
|
||||||
// G1: persist only `naam` — never write the BSN (national ID) to storage.
|
// G1: persist only `naam` — never write the BSN (national ID) to storage.
|
||||||
if (s) sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ naam: s.naam }));
|
if (s) localStorage.setItem(STORAGE_KEY, JSON.stringify({ naam: s.naam }));
|
||||||
else sessionStorage.removeItem(STORAGE_KEY);
|
else localStorage.removeItem(STORAGE_KEY);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user