ADR-013: Entra ID as the AlphaSwarm staff first user pool
- Status: Accepted
- Date: 2026-05-27
- Supersedes: none
- Superseded by: none
- Related rules: AGENTS rule 27 (identity), 42 (TerraformRuntime), 44 (EntraTenantLink approval flow), 26 (CredentialResolver)
- Related ADRs: ADR-003 Auth0 zero-trust
remains valid for B2C / customer-tenant fallback;
ADR-005 separated control plane
is the host for the new
manage.alpha-swarm.aiMSAL routes.
Context
The AlphaSwarm has historically authenticated AlphaSwarm staff through the same Auth0 tenant that serves customer logins. Auth0 has served well as a B2C identity surface, but for the internal staff pool we need:
- Centralised MFA + Conditional Access. Staff already authenticate to Microsoft 365 daily through the corporate Entra tenant. CA policies (block risky sign-ins, named-location MFA, FIDO2 hardware key requirements for admins) are enforced by the IT / Security team in one place. Replicating those controls in Auth0 doubles the surface area.
- Audit centralisation. The corporate SIEM already ingests Entra sign-in logs via the existing log stream. Auth0 audit data has to be exported separately and reconciled.
- Group-driven authorisation. New hires onboard via a single HR-side group action; the Entra group's app-role mapping automatically grants the right AlphaSwarm scopes. The Auth0 path required manual role assignment in the Auth0 dashboard.
- No client secrets in CI. GitHub Actions OIDC + federated
credentials replace the old
AZURE_CLIENT_SECRETrepo secret. - Customer separation. The AlphaSwarm staff Entra tenant is independent
of every customer Entra tenant. Customer tenants continue to flow
through the existing
EntraTenantLinkB2B approval wizard (AGENTS rule 44) — they do NOT land in the staff tenant.
The runtime support for Entra has been in place since the Phase 4
service-mesh rollout (alphaswarm/auth/providers/msal_entra.py); this ADR
formalises the first user pool designation and brings the Entra
configuration under Terraform control.
Decision
- The AlphaSwarm staff Microsoft Entra ID tenant is the first user pool
for
manage.alpha-swarm.ai. Tokens whoseissmatches the AlphaSwarm staff tenant are routed throughMsalEntraIdentityProviderbefore any other provider in the chain. - Auth0 stays as the customer-facing B2C pool and as the degraded-mode fallback for staff (e.g. if the Entra side has an incident).
- Every Entra resource is under Terraform control through the
new
alphaswarm_entra_directorymodule: 3 app registrations + 7 directory groups + 7 app roles + group → role assignments + named locations- GitHub Actions OIDC federated credentials.
- Conditional Access policies remain manually authored because P2 licensing requires Security-team review on every policy change. The Terraform module records policy display names as documentation; a smoke-test helper queries Microsoft Graph at apply time to confirm each named policy exists.
- Apply path is
TerraformRuntimeonly (rule 42). Plan-only on PR; apply on push tomainthroughalphaswarm deploy. - Federated credentials replace static client secrets in CI. The
alphaswarm-ci-githubapp's federated credentials are per-environment + per-branch — wildcards are rejected at plan time.
Alternatives considered
A. Keep Auth0 as the staff pool
Pros:
- Zero migration effort.
- Single identity surface for both staff + customers.
Cons:
- Doubles the MFA + CA enforcement surface.
- Splits audit logs across two systems (Auth0 + corporate SIEM).
- Manual role assignment in the Auth0 dashboard for every new hire.
- Static client secrets in CI.
Rejected: the operational + audit overhead outweighs the zero-migration win.
B. Migrate ALL identity (staff + customers) to Entra
Pros:
- Single user pool overall.
- Strongest audit centralisation.
Cons:
- Customer tenants are operated by their own admins; we can't unify them into a single tenant we control.
- B2C scenarios (e.g. self-service trial signups) are awkward in enterprise Entra; Auth0 + the existing B2C surface is the right tool.
- Migration cost: every existing Auth0 customer connection would need to be re-issued in Entra B2C (different protocol, different SDK).
Rejected: the staff vs customer split is the right granularity.
C. Keep Entra resources in the Azure Portal (no Terraform)
Pros:
- Lower friction for ad-hoc adjustments.
- No Terraform learning curve for IT staff.
Cons:
- No reviewable diff for changes.
- No
terraform_runsaudit row for compliance. - No federated-credential automation; CI keeps a static secret.
- Drift between dev / prod tenants becomes hard to detect.
Rejected: clickops on identity infrastructure violates AGENTS rule 42 in spirit (audit-first, every change reviewed).
Consequences
Positive
- One MFA + CA enforcement surface for staff, owned by Security.
- Audit logs land in the corporate SIEM via the existing log stream.
- New-hire onboarding is one HR-side group add.
- CI authenticates to Azure with no stored secrets.
- Customer Entra tenants flow through the existing B2B path; the internal pool doesn't bleed into customer scope.
- The
alphaswarm_entra_directorymodule is reviewable; every change gets a PR + plan diff.
Negative
- Two identity providers to keep healthy (Entra + Auth0). Mitigated
by
select_provider_for_tokenrouting — the right provider is picked per-request rather than per-deploy. - Bootstrap window has a temporary client secret (Phase 0/1 of the rollout plan); retired in Phase 5.
- CA policies remain a manual surface — the Terraform module can reference them but cannot create them. Mitigated by the smoke-test helper that confirms required policies exist before exposure.
- Group membership is intentionally outside Terraform. HR + Security
own membership; the audit path comes from the Entra audit log
rather than
terraform_runs.
Risks + mitigations
| Risk | Mitigation |
|---|---|
| Tenant lockout (every admin's account gets MFA-locked) | TWO break-glass accounts excluded from CA policies (rollout plan §4 + entra-rotate-secrets runbook). |
| Group → role mapping mistake grants over-privileged access | terraform_runs audit + the daily Prometheus alert on entra_role_assignment_changes_total. |
| Federated-credential subject too broad | Per-environment / per-branch subjects; module rejects wildcards at plan time. |
| Customer-tenant tokens routed to MSAL-internal | select_provider_for_token checks the iss claim against auth_msal_internal_tenant_id; mismatch falls through to Auth0. Unit tested. |
Implementation pointers
- Long-form rollout plan:
docs/plans/entra-internal-tenant-rollout.md - Concepts:
concepts/identity/entra-internal-tenant - Bootstrap runbook:
how-to/entra-terraform-bootstrap - Onboarding runbook:
how-to/entra-onboard-new-staff - Secret-rotation runbook:
how-to/entra-rotate-secrets - Module:
alphaswarm_platform/terraform/modules/alphaswarm_entra_directory/ - Provider runtime:
alphaswarm/auth/providers/msal_entra.py - Provider chain selector:
alphaswarm/auth/providers/__init__.py::select_provider_for_token