Authentication and access control
Authentication and access control
What this system does
The repository supports two overlapping authentication paths:
- a legacy Firebase-based path exposed by
src/contexts/AuthContext.jsx, and - an Azure AD/MSAL path exposed by
src/auth/MsalAuthContext.jsx`.
The app still exports both through src/auth/index.js, and App.jsx/AdminRoute.jsx choose between them based on configuration and runtime availability. The MSAL path is the more complete production flow: it handles Microsoft sign-in, backend token exchange, tenant restoration, consent status, and admin role resolution.
Runtime flow
sequenceDiagram participant Browser participant App as App.jsx participant MSAL as MsalAuthProvider participant Backend as backendAuthService participant Consent as consentService participant Tenant as tenantService
Browser->>App: load application App->>MSAL: read auth state / consent gate MSAL->>Backend: exchange Microsoft token after redirect or popup login Backend-->>MSAL: backend JWT, refresh token, user profile, role, tenant ids MSAL->>Consent: restore or check consent state MSAL->>Tenant: fetch tenants and restore selected tenant Tenant-->>MSAL: tenant list + tenant profile MSAL-->>App: authenticated user, role, consent, tenant contextLegacy Firebase auth
AuthContext is still present for compatibility. It listens to Firebase auth state, exposes user, role, isAdmin, loading/error flags, and sign-in/sign-out helpers, and logs failures through loggingService. In this repository, it is best treated as a compatibility layer rather than the primary identity model.
Important constraints
- Admin detection via email lists is intentionally removed; the code comments note that admin identity should come from Azure AD groups or backend role state instead.
- This context exists mainly to keep older surfaces working while the MSAL path is the authoritative one.
MSAL auth and backend exchange
MsalAuthContext owns the main authentication state machine.
Inputs it consumes
- MSAL accounts and interaction state from
@azure/msal-react. - Group IDs from
msalConfig. BackendAuthContextfor token exchange.tenantServiceandconsentServicefor backend state restoration.
State it owns
user,role, andgroupsfrom Microsoft identity data.backendUser,backendTokenExchanged,tenants,currentTenantId, andtenantBranding.- consent flags:
consentGivenplus local and backend fallbacks.
How role resolution works
Role resolution is deliberately layered:
- tenant branding membership role,
- selected tenant membership role,
- selected tenant role,
- backend user role,
- backend user membership role,
- backend
is_admin, - MSAL role,
- fallback to
user.
The authoritative resolver is resolveEffectiveRole.
Consent and access control
Consent is a first-class gate in the app lifecycle. App.jsx checks consent state before allowing entry into the main workspace, and MsalAuthContext restores consent from local storage or backend state after sign-in and refresh.
Relevant service behavior:
recordConsentposts consent to the backend and stores a local success flag.checkConsentFromBackendverifies backend state on refresh.revokeConsentclears both backend and local consent state.
Tenant selection and persistence
Tenant context is restored after authentication if the backend session exists.
Tenant lifecycle
- Fetch the available tenants for the user.
- Restore the selected tenant from session storage if it still exists.
- Otherwise auto-select the first tenant.
- Load the current tenant profile/branding.
- Persist the active tenant through backend auth helpers so future requests include the tenant header.
tenantService owns the fetch/select/switch/profile APIs. MsalAuthContext consumes it at initialization time and whenever tenant branding needs to be refreshed.
Admin access
AdminRoute is the gate for admin pages. It considers:
- Firebase admin state,
- Azure MSAL admin state, and
- the effective role produced by
resolveEffectiveRole().
[canAccessAdminPortal](../../src/utils/rbac.js) permits access for admin/manager/platform-admin users.
Storage and state boundaries
This auth stack uses persistent browser storage intentionally:
- session storage for backend JWT/user/tenant state and selected tenant identity,
- local storage for consent flags.
That split matters:
- session-scoped values are meant to evaporate when the browser session ends,
- consent must survive logout/re-login until revoked.
Extension points
When changing auth behavior, the canonical places are:
src/auth/msalConfig.jsfor Azure app registration values and group IDs,src/auth/backendAuthService.jsfor exchange/token lifecycle,src/services/consentService.jsfor consent API semantics,src/services/tenantService.jsfor tenant fetch/select behavior,src/utils/rbac.jsfor admin access rules.
Focused tests
tests/unit/rbac.test.jsproves admin access and effective-role precedence.tests/contract/client.test.jsproves auth header propagation and tenant headers in API requests.- The Playwright workflow checks in
e2e/tests/workflow/verify-agent-preflight.spec.jsand related preflight specs exercise authenticated portal flows.