Saltar al contenido principal

Wire alphaswarm_admin against the AlphaSwarm staff Entra tenant

End-to-end procedure for connecting the alphaswarm_admin service (backend BFF + Next.js frontend) to Microsoft Entra ID, using the staff app registration that the alphaswarm_entra_directory Terraform module provisions.

The result: AlphaSwarm staff sign in to manage.alpha-swarm.ai with their corporate Entra account; the admin BFF validates the resulting api://alphaswarm-manage-api access tokens; the SPA mints tokens via @azure/msal-browser and renews them silently with acquireTokenSilent.

Companion runbooks:

What gets wired​

SurfaceWhat it does
alphaswarm_admin/src/alphaswarm_admin/settings.pyReads the ALPHASWARM_AUTH_MSAL_INTERNAL_* env vars set by the helper script. Single-tenant when INTERNAL_TENANT_ID is set; multi-tenant otherwise.
alphaswarm_admin/src/alphaswarm_admin/deps/identity.pyJWT validator pinned to the AlphaSwarm staff Entra v2.0 issuer; verifies aud=api://alphaswarm-manage-api, maps roles claim through the canonical RBAC lattice.
alphaswarm_admin/src/alphaswarm_admin/api/routers/auth_setup.pyNew GET /admin/auth/discovery + GET /admin/auth/health. Discovery feeds the SPA's PublicClientApplication; health confirms the IdP is reachable.
alphaswarm_admin/frontend/components/auth/AuthProvider.tsxReal MSAL flow (loginRedirect, acquireTokenSilent, acquireTokenPopup for step-up). No tenant id hard-coded in the bundle — everything comes from /admin/auth/discovery.
scripts/identity/alphaswarm_admin_entra_setup.pyOperator helper: discovers values from Terraform outputs, prints + optionally writes the env vars, prints the runbook.

Prerequisites​

  • The Terraform stack entra-internal has been planned + applied for the wiley-tech environment (see bootstrap runbook).
  • Admin consent has been granted on the staff app's Graph permissions (./scripts/identity/grant_admin_consent.sh "$STAFF_CID").
  • The EntraTenantLink for the AlphaSwarm staff tenant exists with meta.kind = 'internal' (python scripts/identity/seed_entra_internal_tenant.py --apply).

Step 1 — Generate the env vars​

Every scripts/identity/* script referenced on this page (including grant_admin_consent.sh and seed_entra_internal_tenant.py above) lives in the alphaswarm monolith repo, not alphaswarm_admin — run these commands from an alphaswarm checkout.

# Auto-discover from the Terraform outputs in the wiley-tech env.
python scripts/identity/alphaswarm_admin_entra_setup.py

The script prints two env blocks. Sample output:

# --- Backend env (alphaswarm_admin BFF) ---
ALPHASWARM_ADMIN_AUTH_PROVIDER=msal_entra
ALPHASWARM_ADMIN_AUTH_REQUIRED=true
ALPHASWARM_AUTH_MSAL_INTERNAL_TENANT_ID=12345678-aaaa-bbbb-cccc-deadbeef0000
ALPHASWARM_AUTH_MSAL_INTERNAL_APP_ID=99999999-1111-2222-3333-444444444444
ALPHASWARM_AUTH_MSAL_INTERNAL_AUDIENCE=api://alphaswarm-manage-api
ALPHASWARM_AUTH_OIDC_AUDIENCE=api://alphaswarm-manage-api
ALPHASWARM_ADMIN_ENTRA_TENANT=12345678-aaaa-bbbb-cccc-deadbeef0000
ALPHASWARM_ADMIN_ENTRA_REDIRECT_PATH=/api/auth/entra/callback

# --- Frontend env (alphaswarm_admin/frontend) ---
NEXT_PUBLIC_AQP_AUTH_PROVIDER=msal_entra
NEXT_PUBLIC_AQP_ADMIN_API_URL=http://localhost:8900

To write a .env.alphaswarm_admin.entra file alongside the printout:

python scripts/identity/alphaswarm_admin_entra_setup.py --write-env

The script is intentionally additive: it never overwrites values that weren't generated by it; the operator merges the block into their existing Kubernetes manifests / Helm values / .env.local.

Step 2 — Verify the backend can reach Entra​

Boot the admin BFF (or restart your existing instance) with the env vars sourced:

set -a; source .env.alphaswarm_admin.entra; set +a
uv run alphaswarm-admin # or: python -m alphaswarm_admin.main

Then hit the new health endpoint:

curl -fsSL http://localhost:8900/admin/auth/health | jq .

Expected output:

{
"ok": true,
"auth_enabled": true,
"issuer": "https://login.microsoftonline.com/12345678-aaaa-bbbb-cccc-deadbeef0000/v2.0",
"audience": "api://alphaswarm-manage-api",
"jwks_uri": "https://login.microsoftonline.com/12345678-.../discovery/v2.0/keys",
"discovery_url": "https://login.microsoftonline.com/12345678-.../v2.0/.well-known/openid-configuration",
"key_count": 7
}

If ok=false, the JSON body's stage field tells you what failed (discovery, issuer-mismatch, jwks, jwks-empty). Common causes:

  • Wrong ALPHASWARM_AUTH_MSAL_INTERNAL_TENANT_ID → fix the env var, restart.
  • Tenant restrictions block the BFF from reaching login.microsoftonline.com → talk to Network about egress.

Step 3 — Verify discovery returns the frontend config​

curl -fsSL http://localhost:8900/admin/auth/discovery | jq .

Expected:

{
"provider": "msal_entra",
"auth_enabled": true,
"issuer": "https://login.microsoftonline.com/.../v2.0",
"audience": "api://alphaswarm-manage-api",
"scopes": ["api://alphaswarm-manage-api/.default"],
"jwks_uri": "...",
"authority": "https://login.microsoftonline.com/...",
"client_id": "99999999-...",
"tenant_id": "12345678-...",
"redirect_path": "/api/auth/entra/callback",
"claims_namespace": "https://alphaswarm.internal/"
}

The frontend fetches this on mount; no tenant ids land in the JS bundle.

Step 4 — Boot the frontend with MSAL​

cd alphaswarm_admin/frontend
# .env.local picks up NEXT_PUBLIC_* automatically.
pnpm dev
open http://localhost:3001

The first page load triggers the AuthProvider to:

  1. fetch('/admin/auth/discovery') against the BFF.
  2. Lazy-import @azure/msal-browser.
  3. Construct a PublicClientApplication with the discovered config.
  4. Call handleRedirectPromise() (consumes any pending login round-trip).
  5. Surface the active account via useAuth().

A signed-in user should see their name + roles in the dashboard header within a few seconds.

Step 5 — End-to-end smoke test​

The repo's MSAL round-trip helper validates the full chain:

python scripts/identity/verify_entra_login.py

Expected:

INFO Got access token: eyJ0… (1456 chars)
INFO Claims look correct.
INFO CA policies found: AlphaSwarm-Admins-MFA-Required, AlphaSwarm-Block-Risky-Sign-Ins
INFO All checks passed.

How auth is enforced at runtime​

Every subsequent call:

  1. SPA pulls the bearer via acquireTokenSilent.
  2. Backend require_admin dependency validates issuer + audience + signature against the cached JWKS, expands roles through alphaswarm_core.auth.rbac.expand_role.
  3. Step-up routes (require_admin_step_up) trigger acquireTokenPopup for a fresh MFA evaluation.

Local dev (no Entra tenant needed)​

Set:

export ALPHASWARM_ADMIN_AUTH_REQUIRED=false
# or:
export NEXT_PUBLIC_AQP_AUTH_PROVIDER=mock

Both backend and frontend fall back to a synthetic anonymous user with admin:cluster scope. The dashboard renders without any IdP round-trip — ideal for offline contributors.

Troubleshooting​

SymptomCause / Fix
GET /admin/auth/health → 502 stage=discoveryBFF cannot reach login.microsoftonline.com. Check egress.
GET /admin/auth/health → 502 stage=issuer-mismatchThe configured tenant id doesn't match the tenant that responded. Double-check ALPHASWARM_AUTH_MSAL_INTERNAL_TENANT_ID.
Frontend stuck on the loading spinnerInspect the browser console. The most common message is discovery missing client_id/authority — the BFF returned an incomplete discovery doc, meaning ALPHASWARM_AUTH_MSAL_INTERNAL_APP_ID is empty.
Login completes but the user has no rolesThe user isn't in any AlphaSwarm-* directory group, or the staff app's API permission consent wasn't granted. Re-run grant_admin_consent.sh.
401 on every API call after loginThe bearer's aud doesn't match what the BFF expects. Check that the SPA's scopes came from /admin/auth/discovery (so they include api://alphaswarm-manage-api/.default).
Step-up popup never appearssetStepUpSupported(false) was set because the SPA fell back to mock. Confirm NEXT_PUBLIC_AQP_AUTH_PROVIDER=msal_entra.

Production deployment notes​

The same env vars apply in production. In Kubernetes you typically:

  1. Sync the values into a Secret via the External Secrets operator, sourcing from secret/alphaswarm/admin/entra/* in Vault.
  2. Mount the Secret as env on the alphaswarm-admin Deployment.
  3. Build the frontend image with NEXT_PUBLIC_* baked in (Next.js inlines these at build time).

The Terraform module alphaswarm_entra_directory already creates the staff app with the production redirect URI https://manage.alpha-swarm.ai/api/auth/entra/callback; the helper script's --admin-origin defaults to http://localhost:3001 for dev, override to https://manage.alpha-swarm.ai for production manifests.

Audit trail​

Every Entra-side mutation lands in:

  • The Entra audit log — exported to the corporate SIEM via the existing log stream.
  • The AlphaSwarm terraform_runs ledger for every Terraform apply on the entra-internal stack.
  • The AlphaSwarm audit log (Phase 7 §10) on the admin side — require_admin attaches the user's oid to every workload_runs row, so the admin's mutation surface is fully attributed.