diff --git a/src/Web/StellaOps.Web/src/app/core/auth/auth-session.store.ts b/src/Web/StellaOps.Web/src/app/core/auth/auth-session.store.ts index 999daa2b4..eeca6daa8 100644 --- a/src/Web/StellaOps.Web/src/app/core/auth/auth-session.store.ts +++ b/src/Web/StellaOps.Web/src/app/core/auth/auth-session.store.ts @@ -38,9 +38,27 @@ export class AuthSessionStore { () => 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( () =>