Authentication Flow (detail)¶
Re-verified 2026-09-16 against
api/auth.py, the shared deployment defaults, andterminal/entrypoint.sh.
- SPA loads → MSAL acquires an ID token + access token for the app's API audience via Auth Code + PKCE.
- SPA calls
/api/*withAuthorization: Bearer <access_token>. - The
apisidecar (FastAPI) validates the JWT (issuer, audience, signing keys cached from the tenant's OpenID metadata) before any business logic runs. Trusted automation may instead sendX-ELB-API-TokenwhenALLOW_OPENAPI_TOKEN_AUTH=true; the shared deployment default is ON, while an unset process fallback is OFF. A wrong presented shared token fails with 401 and never falls through to bearer auth. - For ARM and data-plane calls, the backend uses the shared user-assigned Managed Identity
id-elb-dashboard-*(mounted on the Container App and visible to all six sidecars) viaDefaultAzureCredential. The bearer token is used only for identity verification (who is calling), not for downstream Azure calls. This avoids OBO consent issues and removes the need forAPI_CLIENT_SECRET. - The MI must be pre-granted sufficient RBAC roles (see docs/architecture/authentication.md §1 for the full matrix). Runtime role assignments (e.g. granting AcrPull to AKS kubelet) are best-effort — if the MI lacks
User Access Administrator, the code logs a one-lineaz role assignment createrecovery hint instead of failing. - The
terminalsidecar bootstrapsaz login --identity --client-id $AZURE_CLIENT_IDfor the programmatic exec cache used by api/worker CLI calls. Browser shells use separate per-operatorAZURE_CONFIG_DIRdirectories and requireaz login --use-device-code(or an explicit identity login) on first use./home/azureuseris ephemeral, so interactive login state disappears on a revision swap; the exec cache is recreated automatically at startup.
Design choice: We intentionally use Managed Identity instead of OBO. OBO requires
API_CLIENT_SECRETand multi-resource consent, which are fragile in single-tenant research environments. MI simplifies deployment at the cost of the MI needing broad permissions — acceptable because the MI is scoped to the Container App and auditable via Azure Monitor.
Troubleshooting: workspace cards show "Ready/OK" but data looks wrong¶
When the dashboard's AKS / Storage / ACR cards show "Ready" or "OK" but the
fields underneath are blank (SKU: ?, IMAGES BUILT: 0/0), the most likely
cause is one of:
- Stale wizard settings.
SetupWizardsaves the chosen Subscription / Resource Group / ACR / Storage names tolocalStorage. If the targeted resources were renamed or deleted, the SPA keeps calling the old names and the backend responds with{"degraded": true, "degraded_reason": "not_found", ...}. - Wrong Azure tenant. The configured subscription belongs to a different
tenant than the credential used by the backend. In a deployed revision that
means a deployment/config mismatch; in host-mode development it commonly
means the developer's
az loginorAZURE_CONFIG_DIRpoints elsewhere. ARM returnsInvalidAuthenticationTokenTenant, the monitor router classifies it asauth_wrong_tenant, and the backend returns an empty payload. - Missing role assignment. The dashboard MI lacks the management/data role needed by the card, or the caller fails the dashboard entry RBAC gate. See Authentication & Authorization.
What the SPA does automatically¶
MonitorCardchips no longer say "OK" / "Ready" when the payload is degraded — they switch to "Wrong tenant", "No access", "Not found", etc., with the full reason in the tooltip.WorkspaceDiagnosticsBanner(above the dashboard grid) renders only when the issue is workspace-wide: a single forbidden card on one leaf resource is left to that card alone, but anyauth_wrong_tenantor two-or-more cards with auth/not-found issues triggers the banner.- The banner's Reset workspace button calls
clearConfig()and re-runs the setup wizard so the user can re-enter the right values from scratch.
How to fix it manually¶
# Case 1 — stale wizard settings
# In the SPA: click "Reset workspace" in the diagnostics banner, or in the
# browser devtools console:
localStorage.clear(); location.reload();
# Case 2 — wrong tenant
az login --tenant <correct-tenant-id>
az account set --subscription <subscription-id-shown-in-spa>
# Case 3 — missing role
# Use /api/me/access-review or scripts/dev/check-mi-rbac.sh to identify whether
# the caller or dashboard MI is missing a role, then grant it at the target RG/resource.
Backend taxonomy¶
The classification is owned by
api/routes/monitor/common.py::_classify_exception
and consumed by
web/src/utils/monitorDegraded.ts.
Codes are part of the SPA contract; renaming one requires changing both
files together. Pinned codes:
| Reason | When | UI label |
|---|---|---|
auth_wrong_tenant |
ARM 401 with InvalidAuthenticationTokenTenant / AADSTS50020 |
"Wrong tenant" (danger) |
unauthorized |
ARM 401 (other) | "Auth required" (danger) |
forbidden |
ARM 403 | "No access" (warning) |
not_found |
ARM 404 / ResourceNotFoundError |
"Not found" (muted) |
azure_error |
Other AzureError |
"Azure error" (warning) |
http_<status> |
Other HTTP errors | "Degraded" (warning) |