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

6.8 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 }), 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. After a successful sign-in, the frontend remembers the password typed by the user in sessionStorage so the profile password-change form can prefill Current password. This is session-scoped browser state only; the backend never returns or stores plaintext passwords.
  7. useAuthSession.signOut calls POST /api/auth/signout; the backend clears the auth cookie and the frontend clears the remembered session password.
  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. 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.