94 lines
6.0 KiB
Markdown
94 lines
6.0 KiB
Markdown
# 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 }`), `campus`, `staffProfile`, and `permissions`. The UI role is derived from `app_role.name` (one of the 11 role names); there is no separate `productRole`.
|
||
6. UI-facing `StaffProfile` is derived in `frontend/src/business/auth/mappers.ts`.
|
||
7. `useAuthSession.signOut` calls `POST /api/auth/signout`; the backend clears the auth cookie.
|
||
8. `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. 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. 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`). All are `globalAccess`-aware: the two system roles (`super_admin`/`system_admin`) carry no permission rows but pass every check, mirroring the backend bypass.
|
||
- **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 role-based via `MODULES[].roles` (it matches the backend permission matrix). `ModuleRouteGuard` renders the 404 page for a forbidden direct URL; `IndexRedirect` lands each role on its first accessible module (so e.g. students/guardians land on the external pages, not the dashboard).
|
||
|
||
## 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, staff profile 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, campus assignments, and staff profiles.
|
||
|
||
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.
|