Skip to content

ADR-036: Azure Authentication Research and Strategy

Status: Accepted
Date: 2024-10-15
Deciders: Architecture Team, Security Team
Related: ADR-008 (Azure Authentication for Local Docker Testing)

Context

Implementing secure Azure authentication across multiple deployment scenarios proved complex:

  1. Local Development: Developers need quick, secure access
  2. Docker Testing: Containers can't use interactive login
  3. DevContainers: Remote development environments
  4. CI/CD Pipelines: Automated deployments
  5. Azure Production: Managed services without secrets

Each scenario has different constraints: - Security requirements - Available authentication mechanisms - Developer experience priorities - Operational complexity

Research Findings

Authentication Mechanisms Evaluated

1. Interactive Login (az login)

How it works:

az login
# Opens browser for Azure AD authentication
# Stores credentials in ~/.azure/

Pros: - ✅ Most secure (no secrets in files) - ✅ Uses your Azure AD identity - ✅ Multi-factor authentication support - ✅ Automatic credential refresh - ✅ Works with DefaultAzureCredential

Cons: - ❌ Requires browser access - ❌ Doesn't work in containers - ❌ Not suitable for CI/CD - ❌ Credentials expire (need re-login)

Decision: Use for local development

2. Service Principal (Client ID + Secret)

How it works:

# Create Service Principal
az ad sp create-for-rbac --name "app-name" --role "Cognitive Services OpenAI User"

# Outputs:
{
  "appId": "client-id",
  "password": "client-secret",
  "tenant": "tenant-id"
}

# Use in environment
export AZURE_TENANT_ID="tenant-id"
export AZURE_CLIENT_ID="client-id"
export AZURE_CLIENT_SECRET="client-secret"

Pros: - ✅ Works in containers - ✅ Works in CI/CD - ✅ Programmatic authentication - ✅ Works with DefaultAzureCredential - ✅ Can be rotated

Cons: - ⚠️ Secrets must be stored securely - ⚠️ Need to rotate every 90 days - ⚠️ Risk of secret exposure - ⚠️ Manual management required

Decision: Use for Docker testing

3. Managed Identity (Azure Production)

How it works:

# No configuration needed!
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()

# Azure automatically provides identity
# No secrets, no configuration

Pros: - ✅ No secrets to manage - ✅ Automatic credential rotation - ✅ Azure-native security - ✅ RBAC-based permissions - ✅ Best security practice - ✅ Works with DefaultAzureCredential

Cons: - ❌ Only works in Azure - ❌ Can't test locally - ❌ Requires Azure resource deployment

Decision: Use for Azure production

4. Access Tokens (Short-lived)

How it works:

# Generate token (expires in 1 hour)
az account get-access-token --resource https://cognitiveservices.azure.com

# Use token
export AZURE_ACCESS_TOKEN="eyJ0eXAi..."

Pros: - ✅ Works in containers - ✅ No long-lived secrets - ✅ Inherits your permissions

Cons: - ❌ Expires in 1 hour - ❌ Not suitable for long-running services - ❌ Manual refresh required - ❌ Complex to manage

Decision: Not recommended

5. Connection Strings / API Keys

How it works:

# Get API key from Azure Portal
export AZURE_OPENAI_KEY="abc123..."
export AZURE_OPENAI_ENDPOINT="https://..."

Pros: - ✅ Simple to use - ✅ Works everywhere

Cons: - ❌ Least secure - ❌ Keys in plaintext - ❌ No automatic rotation - ❌ Not recommended by Microsoft - ❌ No RBAC integration

Decision: Not recommended

DefaultAzureCredential Chain

Microsoft's DefaultAzureCredential tries authentication methods in order:

1. Environment variables (AZURE_CLIENT_ID, etc.)
2. Managed Identity (in Azure)
3. Azure CLI (az login)
4. Visual Studio Code
5. Azure PowerShell
6. Interactive browser

Key insight: One authentication code works across all environments! 🎯

from azure.identity import DefaultAzureCredential

# Works everywhere with proper setup
credential = DefaultAzureCredential()
client = AzureOpenAIClient(endpoint=endpoint, credential=credential)

Decision Matrix

Scenario Method Setup Security Experience
Local Dev az login Easy ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
Docker Test Service Principal Medium ⭐⭐⭐⭐ ⭐⭐⭐
DevContainers az login* Easy ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
CI/CD Service Principal Medium ⭐⭐⭐⭐ ⭐⭐⭐⭐
Azure Prod Managed Identity Auto ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

*DevContainers can forward az login credentials

Adopted Strategy

# One-time setup
az login

# Run application - authentication automatic!
uv run python -m loan_defenders.api.app

No credentials in files! Most secure and easiest.

Stage 2: Docker Testing

# One-time setup (per 90 days)
az ad sp create-for-rbac --name "loan-defenders-docker"

# Add to .env (never commit!)
AZURE_TENANT_ID=xxx
AZURE_CLIENT_ID=xxx
AZURE_CLIENT_SECRET=xxx

# Run containers
docker-compose up

Credentials in .env (git-ignored), rotated regularly.

Stage 3: Azure Production

# Container App configuration
identity:
  type: SystemAssigned

# RBAC assignment (via Bicep)
roleAssignments:
  - principalId: containerApp.identity.principalId
    roleDefinitionId: "Cognitive Services OpenAI User"

No configuration needed! Managed Identity automatic.

Implementation Guide

Setup for Each Environment

Local Development:

# 1. Login to Azure
az login

# 2. Verify
az account show

# 3. Run app (no .env credentials needed)
cd apps/api
uv run python -m loan_defenders.api.app

Docker Testing:

# 1. Create Service Principal (if not exists)
./scripts/setup-azure-auth.sh

# 2. Verify .env has credentials
grep AZURE_ .env

# 3. Start containers
docker-compose up

Azure Production:

# 1. Deploy infrastructure with Managed Identity
cd infrastructure/bicep
../deploy.sh dev loan-defenders-dev-rg --stage all

# 2. Identity and RBAC configured automatically
# 3. Deploy containers (no credentials needed)

Security Best Practices

✅ DO

  • Use az login for local development
  • Store Service Principal secrets in .env (git-ignored)
  • Rotate Service Principal secrets every 90 days
  • Use Managed Identity in Azure production
  • Apply least-privilege RBAC roles
  • Use DefaultAzureCredential in code

❌ DON'T

  • Commit credentials to git
  • Use API keys (use RBAC instead)
  • Share Service Principal secrets
  • Use same credentials for dev/prod
  • Store secrets in plaintext files
  • Hard-code credentials in code

Consequences

Positive

Security: Best practices for each environment
Developer Experience: az login is easiest for devs
Production Security: Managed Identity is most secure
Flexibility: Different auth per scenario
Consistent Code: DefaultAzureCredential works everywhere

Negative

⚠️ Complexity: Different setup per environment
⚠️ Documentation: Need clear guides for each scenario
⚠️ Learning Curve: Developers must understand auth chain
⚠️ Troubleshooting: Different failure modes per method

Mitigations

  • Comprehensive authentication guide created
  • Clear error messages in application
  • Automated setup scripts provided
  • Documentation per deployment stage

References

Status

Researched: October 2024
Implemented: Across all deployment stages
Current State: Working in local, Docker, and Azure environments
Documentation: Comprehensive guides completed

Lessons Learned

  1. DefaultAzureCredential is powerful - One code path for all environments
  2. az login best for developers - Security + ease of use
  3. Managed Identity is ideal - But only available in Azure
  4. Service Principals needed - For Docker and CI/CD
  5. Documentation critical - Authentication is confusing without clear guides