MSAL clientId placeholder guard + local-run auto-pull¶
Date: 2026-05-22
Scope: web/.env.example, web/src/config/runtime.ts, web/src/auth/msal.ts, web/src/App.tsx, scripts/dev/local-run.sh
Motivation¶
A user who cloned the repo locally and signed in with a different Entra account
hit AADSTS700038: 00000000-0000-0000-0000-000000000000 is not a valid
application identifier.
Root cause: web/.env.example shipped a placeholder all-zero UUID for
VITE_AZURE_CLIENT_ID. Anyone following the standard "copy .env.example to
.env.local" onboarding ended up baking that placeholder into the Vite build.
The existing App.tsx CLIENT_ID_MISSING guard (!configValue(...)) only
detected the empty case, so the placeholder UUID slipped past and reached
MSAL, which dutifully sent it to AAD.
The deployed pipeline was never affected — scripts/dev/postprovision.sh
already creates/reuses an Entra App Registration and injects the resolved
clientId via --build-arg VITE_AZURE_CLIENT_ID=$API_CLIENT_ID_VAL plus a
Bicep env var on the frontend sidecar.
User-facing change¶
- Local clone-and-run no longer silently fails with AADSTS700038. If the clientId is empty or the all-zero placeholder or any non-UUID string, the SPA renders the existing "Setup Required" screen instead of initialising MSAL.
scripts/dev/local-run.sh webauto-pullsAPI_CLIENT_IDfromazd env get-valueswhenVITE_AZURE_CLIENT_IDis unset or placeholder, so a developer who already ranazd updoes not have to hand-editweb/.env.local.
API / IaC diff summary¶
web/.env.example: clearedVITE_AZURE_CLIENT_IDto empty with an explanatory comment.web/src/config/runtime.ts: addedisUsableClientId()(rejects empty, all-zero UUID, and any non-UUID string) andazureClientId()accessor.web/src/auth/msal.ts: usesazureClientId(); warning message updated to point at the auto-pull path.web/src/App.tsx:CLIENT_ID_MISSINGnow usesisUsableClientId().scripts/dev/local-run.sh(webcase): whenVITE_AZURE_CLIENT_IDis empty or the all-zero placeholder, queryazd env get-values | awkforAPI_CLIENT_IDand export it for thenpm run devchild process.docs/joining-existing-deployment.md: new dedicated page documentingazd env refreshas the supported way for a second teammate to bind a fresh clone to an existing deployment, plus the manual-clientId fallback, plus the "RBAC for the new teammate" sub-section that clarifies the deployed SPA needs no per-user workload RBAC and documentsgrant-local-rbac.sh --user <upn>for the deployer-grants case.docs/troubleshooting.md: new symptom-first index covering Setup Required / AADSTS700038, access_denied (deployed MI vs local user), network_blocked, missing workspace tag, AADSTS50011 redirect URI mismatch, staleweb/.env.local, andazd env refreshwithout a selected env.docs/get-started.md: the previous inline "Join An Existing Deployment" section was removed (it sat awkwardly after Cost And Cleanup) and replaced with a short pointer to the two new pages.docs/deployment-reference.md: existingaccess_denied/network_blockedrecovery snippet now also documents the--userflag.mkdocs.yml: new top-level nav entries for Joining An Existing Deployment and Troubleshooting, slotted between Get Started and Deployment Reference.README.md: short pointer in "Local development" linking to the new pages.- No infra / Bicep / Container App changes.
Validation¶
cd web && npm run build→ succeeds (built in 7.71s).get_errorson the three edited TS files → no errors.- Backend tests unaffected; not re-run.
- Manual: copying
.env.example→.env.localand runningnpm run devwith no other env now shows the "Setup Required" screen instead of the AADSTS700038 popup.