40227-vm/frontend/docs/auth-integration.md
2026-06-12 06:55:35 +02:00

6.0 KiB
Raw Blame History

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.