Fix misleading auth token storage in sessionStorage

- Remove JWT token and expires_at from sessionStorage
- Simplify AuthProvider to use boolean isAuthenticated flag
- Persist only isAuthenticated boolean for page-reload continuity
- Update AuthProvider test to verify new auth model
- Add comprehensive auth documentation to Web-Development.md explaining:
  - Cookie-based authentication model
  - How frontend auth state persists
  - Why tokens are no longer stored
  - Error handling flow for 401/403 responses

The authentication model is cookie-based: the backend sets bangui_session
cookie on login, frontend automatically includes it via credentials:
'include', and the backend is the sole authority on session validity.
Previously stored tokens were never actually used and made the auth model
misleading during development.

Fixes TASK-STATE-04.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-23 09:30:29 +02:00
parent 941502b710
commit f3d6574160
4 changed files with 67 additions and 64 deletions

View File

@@ -485,7 +485,42 @@ if (data.length > MAX_VISIBLE_BANS) { ... }
---
## 11. Error Handling
## 10. Authentication
### Session Model
The authentication model is **cookie-based**:
1. **Login:** The frontend sends the master password (SHA256-hashed) to `POST /api/auth/login`. The backend validates it, creates a session, and returns an HTTP response with a `Set-Cookie` header containing `bangui_session`.
2. **Requests:** All API requests automatically include the session cookie via `credentials: "include"` in the fetch options. The frontend does **not** send an Authorization header or token in the request body.
3. **Session validity:** The backend is the **sole authority** on whether a session is valid. The frontend is authenticated when the backend accepts the request (returns 2xx) and is not authenticated when the backend rejects it (returns 401 or 403).
4. **Logout:** The frontend sends `POST /api/auth/logout`, and the backend invalidates the session and clears the cookie.
### Frontend Auth State
- The `AuthProvider` context (`providers/AuthProvider.tsx`) manages a simple boolean `isAuthenticated` state.
- On successful login, `isAuthenticated` is set to `true` and persisted to `sessionStorage` for page-reload continuity.
- On logout or when `SESSION_EXPIRED_EVENT` fires (triggered by a 401/403 API response), `isAuthenticated` is set to `false` and cleared from `sessionStorage`.
- The `sessionStorage` entry (`bangui_authenticated`) survives page refreshes within the same tab but is automatically cleared when the tab closes.
- The session cookie persists according to the backend's cookie settings (typically for the duration of the browser session or as configured server-side).
### Why Not Token-Based?
The frontend previously stored JWT tokens in `sessionStorage` but never actually used them. The authentication model is entirely cookie-based (handled by the browser automatically), making stored tokens confusing and misleading. If token-based auth is needed in the future, the storage approach would need to change significantly (e.g., to include Authorization headers in all requests). For now, the only persistent state the frontend needs is the boolean `isAuthenticated` flag.
### Error Handling
When an API request returns 401 or 403:
1. The `client.ts` module dispatches a `SESSION_EXPIRED_EVENT`.
2. The `AuthProvider` listener handles it by clearing `isAuthenticated` and redirecting to `/login`.
3. Hooks must use `handleFetchError` (from `utils/fetchError.ts`) to avoid displaying auth errors as user-facing error messages.
---
## 12. Error Handling
- Wrap API calls in `try-catch` inside hooks — components should never see raw exceptions.
- **All hook catch blocks must use `handleFetchError` rather than directly calling `setError`.** This ensures auth errors (401/403) are routed to the global session-expiry flow instead of displaying confusing error text in the UI. Use the pattern: `handleFetchError(err, setError, "User-friendly fallback message")`.
@@ -496,7 +531,7 @@ if (data.length > MAX_VISIBLE_BANS) { ... }
---
## 12. Performance
## 13. Performance
- Use `React.memo` only when profiling reveals unnecessary re-renders — do not wrap every component by default.
- Use `useMemo` and `useCallback` for expensive computations and stable callback references passed to child components — not for trivial values.
@@ -506,7 +541,7 @@ if (data.length > MAX_VISIBLE_BANS) { ... }
---
## 13. Accessibility
## 14. Accessibility
- Use semantic HTML elements (`<button>`, `<nav>`, `<table>`, `<main>`, `<header>`) — not `<div>` with click handlers.
- Every interactive element must be **keyboard accessible** (focusable, operable with Enter/Space/Escape as appropriate).
@@ -517,7 +552,7 @@ if (data.length > MAX_VISIBLE_BANS) { ... }
---
## 14. Testing
## 15. Testing
- Write tests for every new component, hook, and utility function.
- Use **Vitest** (or Jest) as the test runner and **React Testing Library** for component tests.