fix(auth): persist session to localStorage for cross-tab support
Session metadata and full session now written to both sessionStorage and localStorage so that new tabs and window.open() inherit the auth state without requiring a fresh login. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -144,12 +144,12 @@ export class AuthSessionStore {
|
||||
private readPersistedMetadata(
|
||||
restoredSession: AuthSession | null
|
||||
): PersistedSessionMetadata | null {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
const raw = localStorage.getItem(SESSION_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
if (!restoredSession) {
|
||||
return null;
|
||||
@@ -165,7 +165,7 @@ export class AuthSessionStore {
|
||||
typeof parsed.issuedAtEpochMs !== 'number' ||
|
||||
typeof parsed.dpopKeyThumbprint !== 'string'
|
||||
) {
|
||||
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
localStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
return restoredSession ? this.toMetadata(restoredSession) : null;
|
||||
}
|
||||
const tenantId =
|
||||
@@ -180,37 +180,48 @@ export class AuthSessionStore {
|
||||
tenantId,
|
||||
};
|
||||
} catch {
|
||||
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
localStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
return restoredSession ? this.toMetadata(restoredSession) : null;
|
||||
}
|
||||
}
|
||||
|
||||
private readPersistedSession(): AuthSession | null {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
// Try sessionStorage first (same-tab, survives F5), then fall back
|
||||
// to localStorage (cross-tab, survives new-tab / window.open).
|
||||
const fromSession = this.readSessionFrom('session');
|
||||
if (fromSession) {
|
||||
return fromSession;
|
||||
}
|
||||
return this.readSessionFrom('local');
|
||||
}
|
||||
|
||||
private readSessionFrom(storage: 'session' | 'local'): AuthSession | null {
|
||||
const store = storage === 'session' ? sessionStorage : localStorage;
|
||||
if (typeof store === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = sessionStorage.getItem(FULL_SESSION_STORAGE_KEY);
|
||||
const raw = store.getItem(FULL_SESSION_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(raw) as AuthSession;
|
||||
if (!this.isValidSession(parsed)) {
|
||||
sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY);
|
||||
store.removeItem(FULL_SESSION_STORAGE_KEY);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parsed.tokens.expiresAtEpochMs <= Date.now()) {
|
||||
sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY);
|
||||
store.removeItem(FULL_SESSION_STORAGE_KEY);
|
||||
this.restoredSessionExpired = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
} catch {
|
||||
sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY);
|
||||
store.removeItem(FULL_SESSION_STORAGE_KEY);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -262,31 +273,28 @@ export class AuthSessionStore {
|
||||
}
|
||||
|
||||
private persistMetadata(metadata: PersistedSessionMetadata): void {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(metadata));
|
||||
localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(metadata));
|
||||
}
|
||||
|
||||
private persistSession(session: AuthSession): void {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
sessionStorage.setItem(FULL_SESSION_STORAGE_KEY, JSON.stringify(session));
|
||||
const json = JSON.stringify(session);
|
||||
try { sessionStorage.setItem(FULL_SESSION_STORAGE_KEY, json); } catch { /* SSR / quota */ }
|
||||
try { localStorage.setItem(FULL_SESSION_STORAGE_KEY, json); } catch { /* SSR / quota */ }
|
||||
}
|
||||
|
||||
private clearPersistedMetadata(): void {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
localStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
}
|
||||
|
||||
private clearPersistedSession(): void {
|
||||
if (typeof sessionStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY);
|
||||
try { sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY); } catch { /* SSR */ }
|
||||
try { localStorage.removeItem(FULL_SESSION_STORAGE_KEY); } catch { /* SSR */ }
|
||||
}
|
||||
|
||||
getActiveTenantId(): string | null {
|
||||
|
||||
Reference in New Issue
Block a user