40227-vm/frontend/docs/auth-integration.md

94 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Auth Integration
## Purpose
The product frontend authenticates through backend-owned session cookies.
The backend transport details are documented in `backend/docs/cookie-auth.md`.
## Runtime Configuration
The frontend reads only public browser-safe API configuration from:
- `VITE_BACKEND_API_URL`
Local values live in ignored `frontend/.env` files; the template value is documented in `frontend/.env.example`.
Do not add secrets to frontend env files. Vite exposes `VITE_*` values to the browser bundle.
## Auth Flow
1. `AuthContext` delegates auth state to `useAuthSession` from `frontend/src/business/auth/hooks.ts`.
2. `useAuthSession.signIn` calls `POST /api/auth/signin/local` through `frontend/src/shared/api/auth.ts`.
3. The backend sets an HttpOnly auth cookie and returns the current user profile.
4. `useAuthSession` restores the session with `GET /api/auth/me`.
5. The backend returns the current product profile, including `app_role` (`{ id, name, scope, globalAccess }`), direct tenant scope (`organizationId` / `schoolId` / `campusId` / `classId`), `avatar`, `phoneNumber`, and effective `permissions`. The UI role is derived from `app_role.name` (one of the first-class role names); there is no separate `productRole`.
6. `useAuthSession.signOut` calls `POST /api/auth/signout`; the backend clears the auth cookie.
7. `SignInModal` delegates modal mode, form draft state, validation, and submit workflow to `useAuthModalWorkflow`.
## Refresh Tokens
The refresh flow keeps tokens backend-owned:
1. Backend sign-in sets short-lived access and long-lived refresh HttpOnly cookies.
2. Protected API requests use the access cookie only.
3. `POST /api/auth/refresh` uses the refresh cookie only, rotates it server-side, sets fresh cookies, and returns the current user profile.
4. `httpClient` performs one controlled refresh-and-retry after an access-expiry `401`. A `403` is treated as **forbidden, not expired** — it surfaces as an `ApiError` (no refresh, no logout) and a global `QueryCache`/`MutationCache` handler toasts it.
5. If refresh fails because both access and refresh credentials are expired or invalid, the business layer clears the user and redirects to `/login`.
6. React Query does not retry `AuthExpiredError` or `401` `ApiError` responses, so an expired session does not fan out into repeated protected API calls.
7. Non-auth backend failures remain observable errors; no infinite retry and no silent fallback.
The frontend must not read, store, or receive access or refresh token values.
## Login Route
The app exposes `/login` as the deterministic destination for expired sessions and for unauthenticated visitors. An `AuthGuard` (`frontend/src/app/AuthGuard.tsx`) wraps the shell and redirects unauthenticated users to `/login`; the shell is authenticated-only and stays unmounted while `/auth/me` is still resolving. The previous in-shell guest-preview experience (guest banner, guest role picker, in-shell sign-in) was removed — sign-in happens on `/login` via the retained `SignInModal`.
Rules:
- `/login` must reuse the existing auth business hook and API functions.
- Auth-expired redirects must use `replace: true`.
- Safe return paths may be preserved only for same-origin product routes.
- Tokens and raw session failure details must never be placed in the URL.
- Expired access plus valid refresh must restore the session without redirecting.
- Expired access plus expired refresh must redirect to `/login` without showing a raw error.
## Authorization (frontend gating)
Authorization is UX-only on the frontend; the backend remains the sole authority. The user's effective permissions arrive on `CurrentUser.permissions` (role permissions `custom_permissions`).
- **Permission vocabulary:** `frontend/src/shared/auth/permissions.ts` — typed `${VERB}_${ENTITY}` + product-feature permission names mirroring the backend.
- **Selectors + hook:** `frontend/src/business/auth/permissions.ts` (`hasPermission`/`hasAnyPermission`/`hasAllPermissions`) and the `usePermissions()` hook (`frontend/src/hooks/usePermissions.ts`). `system_admin` is permission-driven like the tenant roles; `globalAccess` still keeps its platform-wide scope. Only `super_admin` bypasses the standard management/page checks, and personal workflow permissions listed in `GLOBAL_BYPASS_EXCLUDED_PERMISSIONS` (`READ_PARENT_COMM`, `ACK_POLICY`, `ZONE_CHECKIN`) still require an explicit effective permission.
- **Affordance gate:** `<PermissionGate permission|anyOf|allOf>` (`frontend/src/components/auth/PermissionGate.tsx`) hides children the user lacks permission for.
- **Route gating:** module/route visibility is permission- and scope-based via `MODULES[].permissions`, the app-shell scope-tier map, the current effective scope, and the current user's effective `permissions`. `ModuleRouteGuard` renders the 404 page for a forbidden direct URL; `IndexRedirect` lands each user on the first accessible module. When a user drills into or backs out of a tenant scope, the shell replaces an invalid current module route with the first accessible module for the new scope.
## Layering
- View/provider: `frontend/src/contexts/AuthContext.tsx`, `frontend/src/components/frameworks/SignInModal.tsx`, and `frontend/src/components/sign-in-modal/`
- Business logic: `frontend/src/business/auth/`
- API/data access: `frontend/src/shared/api/auth.ts`
- Backend contract types: `frontend/src/shared/types/auth.ts`
## Deferred Product Onboarding
Registration, company creation, campus creation, user creation, and profile updates are intentionally deferred.
The backend has generated auth signup/profile endpoints, but the customer has not approved the product workflow for creating companies, campuses, users, role assignments, and campus assignments.
Rules:
- Do not implement frontend registration or profile creation flows until the backend product contract is defined.
- Do not treat generated auth signup/profile endpoints as the product onboarding contract.
- Track the cross-application onboarding task in `docs/backlog.md`.
- New persisted workflows must use typed backend API modules and business-layer hooks.
## Standards
- Do not duplicate backend role mapping in the frontend.
- Do not add frontend secrets.
- Do not store auth tokens in frontend browser storage.
- Do not expose auth tokens in URLs or API responses.
- Do not add frontend refresh-token storage; refresh uses only backend-owned HttpOnly cookies.
- New persisted workflows must use `frontend/src/shared/api/` through `frontend/src/business/<module>/`.
- New server state should use business-layer hooks built on top of shared API modules.