LCO DMA Authentication Implementation (Dec 19, 2025)¶
Overview¶
This page documents the authentication work delivered on December 19, 2025. The implementation adds Microsoft Entra ID login, frontend auth UX, and API key support as an alternate backend authentication path. It also locks down API routes and adds role-based authorization.
Scope (Today's Commits)¶
f4efc09- Microsoft Entra ID authentication (backend + frontend)6bd1def- Auth UX (overlay, inline prompts) + protected pagesfad4c08- API key authentication + management endpoints
Architecture Summary¶
- Primary auth: Microsoft Entra ID via MSAL (frontend) and
fastapi-azure-auth(backend) - Alternate auth: API keys via
X-API-Keyheader (backend) - Role model:
Reader(read access),Contributor(write + key management) - Protection model: All v1 routers are gated by a shared auth dependency when auth is enabled
Backend Implementation¶
1) Entra ID (JWT) Authentication¶
Core module: backend/app/core/auth.py
- Uses
SingleTenantAzureAuthorizationCodeBearerfromfastapi-azure-auth. - Scope is generated as
api://{AZURE_CLIENT_ID}/user_impersonation. - On request, auth dependency validates the token and stores the user in
request.state.user. - Swagger UI OAuth config is provided when
AZURE_OPENAPI_CLIENT_IDis set. - OpenID config is loaded during application startup when auth is enabled.
Key objects and helpers:
- AuthenticatedUser model (includes roles and auth_type)
- require_auth (base dependency)
- require_reader_role and require_contributor_role
2) Router Protection¶
Main app: backend/app/main.py
- Auth dependency is applied to all v1 routers when
AZURE_AUTH_ENABLED=true(this is the master toggle for enforcement, including API keys). - When auth is disabled, dependencies are not applied and a mock user is used.
Protected routers include:
- Clients, Projects, Services, Crews, Equipment, Materials, WBS, COA, Service Crews, MTO, etc.
- API Keys router (/api/v1/api-keys)
3) API Key Authentication (Alternate Path)¶
Core module: backend/app/core/auth.py
- If
API_KEYS_ENABLED=true, the auth dependency checksX-API-Keyfirst. - Valid API keys create an
AuthenticatedUserwithauth_type="api_key"and role derived from the key. - If no key is present or invalid, it falls back to Entra ID JWT.
Repository: backend/app/repositories/api_key.py
- Keys are generated as
lco_<32 chars> - Stored as bcrypt hashes; plaintext is returned only once on creation
keyPrefix(first 8 characters) is used for partition key and lookuplastUsedAtis updated on successful verification
Cosmos container:
- New container: ApiKeys (partition key: /keyPrefix)
4) API Key Endpoints¶
Router: backend/app/api/v1/routers/api_keys.py
POST /api/v1/api-keys- create key (Contributor only)GET /api/v1/api-keys- list keys (Contributor only)DELETE /api/v1/api-keys/{key_id}- revoke key (Contributor only)
5) Roles & Authorization¶
Role handling:
- Reader OR Contributor -> read access
- Contributor only -> write access and API key management
Roles are sourced from:
- Entra ID token claims (roles claim)
- API key role in Cosmos (Reader or Contributor)
6) Dev Mode / Auth Disabled¶
When AZURE_AUTH_ENABLED=false:
- A mock user is returned with Contributor role
- Router-level dependencies are not applied
- Intended for local development only
7) CORS Security Fix¶
CORS is now explicitly configured with allowed origins (no wildcard).
Frontend Implementation¶
1) MSAL Integration¶
Files:
- frontend/src/auth/msalConfig.ts
- frontend/src/auth/msalInstance.ts
- frontend/src/main.tsx
Key behavior:
- MsalProvider wraps the app
- loginPopup() used for interactive sign-in
- Tokens are acquired silently for API calls
2) Auth State Management¶
Store + hook:
- frontend/src/stores/authStore.ts (Zustand)
- frontend/src/hooks/auth/useAuth.ts
State tracked:
- status: initializing | authenticated | unauthenticated | logging_in | logging_out
- user and roles
- overlayDismissed
3) API Client Token Handling¶
File: frontend/src/services/api/config.ts
- Axios request interceptor acquires access tokens via MSAL
Authorization: Bearer <token>added automaticallyAuthenticationErrorthrown on 401 so UI can suppress generic error toasts
4) Auth UI/UX¶
Components:
- AuthOverlay (dismissible sign-in modal on app load)
- AuthRequired (inline "Sign in to view ..." guard)
- LoginButton (sidebar footer)
Behavior:
- Overlay appears for unauthenticated users until dismissed
- Pages wrap sensitive content with AuthRequired
- Data fetching is skipped when unauthenticated
5) Protected Pages (UI)¶
Wrapped with AuthRequired and guarded against API calls:
- Projects (list, detail, create, edit)
- Universal Tables (crews, crew members, crew trades, equipment, WBS, materials, COA)
- API Keys settings page
Configuration¶
Backend (.env)¶
Set these when auth is enabled:
- AZURE_AUTH_ENABLED=true|false
- AZURE_CLIENT_ID=<app registration client id>
- AZURE_TENANT_ID=<tenant id>
- AZURE_OPENAPI_CLIENT_ID=<optional swagger client id>
- API_KEYS_ENABLED=true|false
- API_KEYS_CONTAINER=ApiKeys
Frontend (.env)¶
VITE_AZURE_CLIENT_ID=<app registration client id>VITE_AZURE_TENANT_ID=<tenant id>VITE_AZURE_API_SCOPE=api://<client_id>/user_impersonationVITE_API_URL=<backend base url>
Operational Notes¶
Calling the API with an API Key¶
Send the API key in the X-API-Key header:
Token-Based Calls¶
Frontend automatically attaches the bearer token. If calling manually:
Key Rotation / Revocation¶
- Keys are only shown once on creation
- Revoke via
DELETE /api/v1/api-keys/{key_id} - Re-create to rotate
Files Touched (Key References)¶
Backend:
- backend/app/core/auth.py
- backend/app/main.py
- backend/app/api/v1/routers/api_keys.py
- backend/app/repositories/api_key.py
- backend/app/schemas/api_key.py
- backend/app/core/config.py
- backend/app/db/cosmos.py
Frontend:
- frontend/src/auth/msalConfig.ts
- frontend/src/auth/msalInstance.ts
- frontend/src/hooks/auth/useAuth.ts
- frontend/src/stores/authStore.ts
- frontend/src/services/api/config.ts
- frontend/src/components/auth/AuthOverlay.tsx
- frontend/src/components/auth/AuthRequired.tsx
- frontend/src/components/auth/LoginButton.tsx
- frontend/src/App.tsx
- frontend/src/main.tsx
Follow-Up Ideas (Optional)¶
- Add
.env.exampleentries for the new auth-related env vars - Add a small integration test that validates API key auth flow
- Add role-based UI badges (Reader vs Contributor) in the sidebar