ADR-001: Entra ID for people, HTTP Basic for the MCP door
Date: 2026-10-20 Status: Decided
Context
Section titled “Context”EstimateKit holds client estimation data and serves on a public hostname on the Cloud Engineering Platform. Every surface (the SPA, /api/*, /mcp) needs gating before the instance serves anything, and a misconfigured deployment must never be silently open. The platform’s identity provider is Microsoft Entra ID; the previous Foundry install used a Keycloak realm behind oauth2-proxy.
Decision
Section titled “Decision”ESTIMATEKIT_AUTH_MODE=entra is the deployment posture. People authenticate against the tenant with the authorization-code flow and PKCE, run by the app at /auth/login and /auth/callback; the app verifies the id_token’s RS256 signature, issuer, audience, nonce, tenant and expiry, then opens a signed session cookie. An unauthenticated browser request is redirected to the tenant; an unauthenticated /api request answers 401.
The /mcp machine door stays on HTTP Basic (ESTIMATEKIT_AUTH_USER and ESTIMATEKIT_AUTH_PASSWORD), because an agent cannot complete an interactive login. With no Basic pair configured, /mcp answers 503.
ESTIMATEKIT_AUTH_MODE=basic keeps the single shared Basic credential for local and fallback use. ESTIMATEKIT_AUTH_DISABLED=true is the explicit local-dev opt-out. Both modes fail closed: an incomplete Entra block, an unknown mode, or missing credentials answers 503 on a protected route rather than serving it. /ping and /api/health stay public so probes work.
The session secret, the Entra client secret and the MCP Basic pair come from Vault through an ExternalSecret. The tenant id, client id and redirect URI are non-secret chart configuration.
Rationale
Section titled “Rationale”- Estimation data is client-confidential; the app refusing to serve unauthenticated is self-contained safety, independent of edge infrastructure.
- Fail-closed means a deployment with a missing Secret is closed, never open, which is what makes publishing the hostname safe.
- Entra ID gives people real identity without a per-user database: the session carries the verified claims, and the app has no user table to keep in step.
- The machine door keeps the one credential an agent can send non-interactively.
Alternatives considered and rejected
Section titled “Alternatives considered and rejected”- Edge-only gating (Cloudflare Access in front): leaves the app open inside the cluster and couples safety to infrastructure the app cannot verify. The microsite module’s SSO is deliberately off; the app gates itself.
- HTTP Basic for people: one shared credential cannot name a person, and the platform’s identity provider is Entra ID.
- A per-user model: there is one operator; Entra ID supplies identity, and a user table would add state the app does not need.