Fix auth session latch: prevent redirects during token refresh
Root cause: AuthSessionStore.isAuthenticated is a computed signal that
returns false during token refresh ('loading' status). Since all routes
use canMatch guards that read isAuthenticated, a token refresh causes
ALL routes to fail guard evaluation simultaneously, redirecting the user
to random pages.
Fix: Add wasEverAuthenticated latch that stays true once set. During
transient 'loading' states, isAuthenticated returns true if the user
was previously authenticated — the session is being refreshed, not lost.
This eliminates the "phantom redirect" bug that made every page in the
app unstable (pages would load then silently navigate away after 1-5
seconds). Verified stable on /setup/identity-access and /evidence/audit-log
with 12-second wait after navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,9 +38,27 @@ export class AuthSessionStore {
|
|||||||
() => this.sessionSignal()?.tokens.expiresAtEpochMs ?? null
|
() => this.sessionSignal()?.tokens.expiresAtEpochMs ?? null
|
||||||
);
|
);
|
||||||
|
|
||||||
readonly isAuthenticated = computed(
|
/**
|
||||||
() => this.sessionSignal() !== null && this.statusSignal() !== 'loading'
|
* True when the user has an active session and is not in a transient
|
||||||
);
|
* 'loading' state. During token refresh the status briefly becomes
|
||||||
|
* 'loading', which would make this computed return false. To prevent
|
||||||
|
* route guards from redirecting away during that transient window, we
|
||||||
|
* latch a `wasEverAuthenticated` flag that stays true for the entire
|
||||||
|
* browser session once the user has logged in at least once. Guards
|
||||||
|
* reading `isAuthenticated` will see true throughout the refresh cycle.
|
||||||
|
*/
|
||||||
|
private wasEverAuthenticated = false;
|
||||||
|
readonly isAuthenticated = computed(() => {
|
||||||
|
const hasSession = this.sessionSignal() !== null;
|
||||||
|
const notLoading = this.statusSignal() !== 'loading';
|
||||||
|
const authenticated = hasSession && notLoading;
|
||||||
|
if (authenticated) {
|
||||||
|
this.wasEverAuthenticated = true;
|
||||||
|
}
|
||||||
|
// During transient 'loading' states, return true if the user was
|
||||||
|
// previously authenticated — the session is being refreshed, not lost.
|
||||||
|
return authenticated || (this.wasEverAuthenticated && this.statusSignal() === 'loading');
|
||||||
|
});
|
||||||
|
|
||||||
readonly tenantId = computed(
|
readonly tenantId = computed(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user