TASK-004: Bootstrap frontend auth state from backend session check
Validates session on app mount by calling GET /api/auth/session instead of relying solely on cached sessionStorage. This ensures the UI state always reflects server reality — expired or revoked sessions are detected immediately. Changes: - Backend: Add GET /api/auth/session endpoint (requires valid session, returns 200/401) - Frontend: Add useSessionValidation hook for mount-time validation - Frontend: Add SessionValidationLoading component for validation spinner - Frontend: Update AuthProvider to call validation on mount with loading state - Frontend: Add validateSession API function - Docs: Update Features.md with session validation behavior - Docs: Update Web-Development.md with session validation pattern Handles three outcomes: 1. Valid session (200): Proceed with cached state 2. Invalid session (401): Clear sessionStorage and redirect to login 3. Network error: Don't logout (backend may be temporarily unreachable) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
74
frontend/src/hooks/useSessionValidation.ts
Normal file
74
frontend/src/hooks/useSessionValidation.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Hook for validating the session on app mount.
|
||||
*
|
||||
* Calls the backend to confirm the session is still valid, rather than relying
|
||||
* solely on cached sessionStorage. Handles network errors gracefully by not
|
||||
* logging out — only explicit 401/403 responses trigger logout.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import * as authApi from "../api/auth";
|
||||
import { ApiError, isAuthError } from "../api/client";
|
||||
|
||||
export interface UseSessionValidationResult {
|
||||
/** `true` when the validation check is in flight. */
|
||||
isLoading: boolean;
|
||||
/** An error if validation failed for a reason other than 401/403. */
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the session on mount.
|
||||
*
|
||||
* This hook is called once during app initialization to confirm the session
|
||||
* is valid on the backend. It handles three cases:
|
||||
*
|
||||
* 1. **Valid session (200)** — `onSessionValid()` is called.
|
||||
* 2. **Invalid session (401/403)** — `onSessionExpired()` is called to log out.
|
||||
* 3. **Network error** — `onNetworkError()` is called. The user is not logged out
|
||||
* because the backend may be temporarily unreachable.
|
||||
*
|
||||
* @param onSessionValid - Callback when session is confirmed valid.
|
||||
* @param onSessionExpired - Callback when session is invalid (401/403).
|
||||
* @param onNetworkError - Callback when a network error occurs.
|
||||
* @returns An object with `isLoading` and `error` states.
|
||||
*/
|
||||
export function useSessionValidation(
|
||||
onSessionValid: () => void,
|
||||
onSessionExpired: () => void,
|
||||
onNetworkError?: (error: Error) => void,
|
||||
): UseSessionValidationResult {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const validate = useCallback(async (): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await authApi.validateSession();
|
||||
onSessionValid();
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && isAuthError(err)) {
|
||||
// Explicit 401/403 — session is invalid or expired.
|
||||
onSessionExpired();
|
||||
} else if (err instanceof Error) {
|
||||
// Network error or other issue — don't log out.
|
||||
setError(err);
|
||||
onNetworkError?.(err);
|
||||
} else {
|
||||
const unknownError = new Error("Unknown error during session validation");
|
||||
setError(unknownError);
|
||||
onNetworkError?.(unknownError);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [onSessionValid, onSessionExpired, onNetworkError]);
|
||||
|
||||
useEffect(() => {
|
||||
void validate();
|
||||
}, [validate]);
|
||||
|
||||
return { isLoading, error };
|
||||
}
|
||||
Reference in New Issue
Block a user