---
tags: [tech-patterns, auto-generated]
source: cc-dashboard
created: 2026-05-07
---

# Azure AD SSO Integration with User Isolation

## When to use
When migrating an existing application from local authentication to Azure AD Single Sign-On (SSO), requiring user account mapping between legacy and SSO identities, with complete isolation of local login mechanisms.

## Prerequisites
- Azure AD tenant with application registration
- Application registered as Single Page Application (SPA) in Azure portal
- JWKS (JSON Web Key Set) endpoint available from Azure AD
- Backend framework supporting JWT token validation (Python/FastAPI recommended)
- Existing user database schema with password_hash and unique identifier columns

## Steps

1. **Register SPA in Azure AD**
   - Add your application URL (e.g., `https://optical-dev.oliver.solutions/cc-dashboard/`) as a Redirect URI in Azure portal
   - Note the Client ID and JWKS endpoint URL

2. **Create SSO validation module** (`src/sso.py`)
   - Implement JWKS token validation
   - Cache public keys from Azure AD JWKS endpoint
   - Validate token signature and claims (issuer, audience, expiration)
   - Extract user identity from token (e.g., `preferred_username` claim)

3. **Update authentication router** (`src/routers/auth.py`)
   - Create new `POST /api/auth/microsoft` endpoint that accepts Azure AD tokens
   - Remove legacy endpoints: `/login`, `/change-password`, `/register`
   - Implement token validation and user lookup/creation in endpoint

4. **Modify user model** (`src/models.py`)
   - Add `azure_oid` column (nullable, unique) to store Azure AD object ID
   - Make `password_hash` nullable to allow SSO-only users
   - Add migration to alter existing schema

5. **Map existing users to SSO identities**
   - For each legacy user requiring SSO access, update `azure_oid` with their Azure AD object ID
   - Example: map `vadymsamoilenko@oliver.agency` → `VadymSamoilenko@oliver.agecny` by setting matching `azure_oid`

6. **Implement development bypass** (`src/auth.py`)
   - Add `DEV_AUTH_BYPASS` flag for local development
   - Auto-login with test user when SSO is unavailable locally
   - Disable this in production environments

7. **Remove password-based authentication**
   - Disable local login endpoints completely
   - Prevent password reset/change operations
   - Ensure all user creation routes require SSO validation

## Key Configuration

```python
# .env or config
AZURE_AD_JWKS_URL=https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys
AZURE_AD_CLIENT_ID=your-client-id
AZURE_AD_TENANT=your-tenant-id
DEV_AUTH_BYPASS=false  # Set to true locally only
```

```python
# src/sso.py - Example validation
import jwt
from functools import lru_cache

@lru_cache(maxsize=1)
def get_jwks():
    # Fetch and cache Azure AD public keys
    pass

def validate_token(token: str):
    # Validate signature using JWKS
    # Check issuer, audience, expiration
    # Return claims
    pass
```

## Gotchas

- **User mapping case sensitivity**: Azure AD usernames may differ in casing from legacy usernames. Implement case-insensitive lookup or explicit mapping table.
- **Token expiration handling**: Azure AD tokens expire quickly. Implement token refresh logic on the frontend; don't rely on long-lived tokens.
- **JWKS caching**: Public keys rotate periodically. Implement cache invalidation or short TTL; don't cache indefinitely.
- **Nullable password fields**: Ensure password validation logic handles null values gracefully; add database constraint to prevent password-less local login attempts.
- **Development environment**: DEV_AUTH_BYPASS must be explicitly disabled in production; verify this during deployment checks.
- **Redirect URI matching**: Azure portal requires exact URL match (protocol, domain, path). Test with full deployment URL before going live.

## Source
Project: cc-dashboard
