obsidian/wiki/connections/fastapi-azuread-docker-trinity.md
2026-04-27 18:22:09 +01:00

3.3 KiB

title description connects created updated
Connection: FastAPI + Azure AD + Docker — The Oliver Trinity These three always appear together in Oliver internal tools — how they wire up and where each touches the other
tech-patterns/fastapi-python-docker
tech-patterns/azure-ad-msal-auth
architecture/docker-compose-fullstack
2026-04-27 2026-04-27

Connection: FastAPI + Azure AD + Docker — The Oliver Trinity

The Connection

FastAPI, Azure AD MSAL, and Docker Compose appear together in almost every Oliver internal tool. They're designed independently but have specific integration points that only become clear across multiple projects.

Key Insight

The auth middleware must run inside the container, but credentials must come from outside it. This creates a specific pattern: Azure AD env vars flow in through Docker Compose env_file, the MSAL JWKS validation runs in FastAPI middleware, and the Docker healthcheck must not hit authenticated endpoints (it has no token).

The three systems interact at exactly three points:

  1. JWT validation: FastAPI reads AZURE_TENANT_ID + AZURE_CLIENT_ID from env → fetches JWKS from Azure → validates tokens from MSAL.js frontend
  2. CORS: FastAPI CORS origins must include the exact frontend origin (no trailing slash) — when running in Docker, the origin is the host's port/domain, not the container's internal address
  3. Local dev bypass: DISABLE_AUTH=true skips Azure AD entirely in dev — this env var must be in the Docker service's env_file, not just the host shell

The Wiring

Browser (MSAL.js PKCE)
    ↓ acquireTokenSilent() → Azure AD → access_token (JWT)
    ↓ Authorization: Bearer {token}
Apache/nginx
    ↓ passes all headers intact (do NOT strip Authorization)
FastAPI middleware (HTTPBearer)
    ↓ fetches JWKS from https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration
    ↓ validates token signature + audience + expiry
    ↓ injects user claims into request.state.user
Route handlers
    ↓ read request.state.user.preferred_username / roles

Environment Variable Pattern

# docker-compose.yml — the right way
services:
  api:
    env_file: ./backend/.env    # contains AZURE_* + DISABLE_AUTH

  frontend:
    environment:
      - VITE_AZURE_CLIENT_ID=${AZURE_CLIENT_ID}
      - VITE_AZURE_TENANT_ID=${AZURE_TENANT_ID}
# backend/.env
AZURE_TENANT_ID=xxx
AZURE_CLIENT_ID=xxx
AZURE_CLIENT_SECRET=xxx   # only if app-only calls needed
DISABLE_AUTH=true          # remove in production

When DISABLE_AUTH Breaks

Teams sometimes forget to remove DISABLE_AUTH=true on the server. Symptoms: authenticated routes return 200 to anyone with no token. Add a startup check:

import os
if os.getenv("DISABLE_AUTH", "false").lower() == "true":
    import logging
    logging.warning("⚠️  AUTH IS DISABLED — do not use in production")

Projects Where This Trinity Appears

GMAL, Mod Comms, Video Accessibility, Semblance, Enterprise Nexus, Barclays Banner Builder, BAIC Dashboard.