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

```yaml
# 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}
```

```env
# 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:

```python
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.

## Related

- [[wiki/tech-patterns/fastapi-python-docker|fastapi-python-docker]]
- [[wiki/tech-patterns/azure-ad-msal-auth|azure-ad-msal-auth]]
- [[wiki/architecture/docker-compose-fullstack|docker-compose-fullstack]]
- [[wiki/architecture/new-project-checklist|new-project-checklist]]
