From a3bdad5be186c48009a6551d857cda222bb13270 Mon Sep 17 00:00:00 2001 From: hansjone Date: Thu, 30 Jul 2026 02:34:25 +0000 Subject: [PATCH] fix(auth): sync logout across browser tabs Listen for localStorage token changes so other open pages clear session state when one tab signs out. Co-authored-by: Cursor --- web/src/auth/AuthContext.tsx | 26 +++++++++++++++++++++++++- web/src/services/api.ts | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/web/src/auth/AuthContext.tsx b/web/src/auth/AuthContext.tsx index 7d7646a..2c6c896 100644 --- a/web/src/auth/AuthContext.tsx +++ b/web/src/auth/AuthContext.tsx @@ -7,7 +7,14 @@ import { useState, type ReactNode, } from "react"; -import { apiGet, apiPost, clearAuthToken, getAuthToken, setAuthToken } from "../services/api"; +import { + AUTH_TOKEN_KEY, + apiGet, + apiPost, + clearAuthToken, + getAuthToken, + setAuthToken, +} from "../services/api"; export type AuthUser = { id: string; @@ -62,6 +69,23 @@ export function AuthProvider({ children }: { children: ReactNode }) { })(); }, [refreshMe]); + // Other tabs keep React auth state until they hear localStorage change. + // `storage` fires only in *other* documents — used to sync logout/login. + useEffect(() => { + const onStorage = (ev: StorageEvent) => { + if (ev.storageArea && ev.storageArea !== localStorage) return; + if (ev.key !== null && ev.key !== AUTH_TOKEN_KEY) return; + if (ev.key === null || ev.newValue == null || ev.newValue === "") { + setToken(null); + setUser(null); + return; + } + void refreshMe(); + }; + window.addEventListener("storage", onStorage); + return () => window.removeEventListener("storage", onStorage); + }, [refreshMe]); + const login = useCallback(async (username: string, password: string) => { const data = await apiPost<{ access_token: string; user: AuthUser }>("/v1/auth/login", { username, diff --git a/web/src/services/api.ts b/web/src/services/api.ts index 5f022c5..57fc327 100644 --- a/web/src/services/api.ts +++ b/web/src/services/api.ts @@ -25,7 +25,7 @@ import type { TopologyMapItem, } from "../types"; -const AUTH_TOKEN_KEY = "netx_access_token"; +export const AUTH_TOKEN_KEY = "netx_access_token"; export const getAuthToken = (): string | null => { try {