Azure Authentication Guide
Complete guide to Azure authentication across all deployment stages: Local Development, Docker Testing, and Azure Production.
Overview
Loan Defenders uses Azure AI Services for LLM capabilities. Authentication varies by deployment stage:
| Stage | Method | Security | Setup Time |
|---|---|---|---|
| Local Development | az login |
⭐⭐⭐⭐⭐ | 1 minute |
| Docker Testing | Service Principal | ⭐⭐⭐⭐ | 5 minutes |
| Azure Production | Managed Identity | ⭐⭐⭐⭐⭐ | Automatic |
Key Concept: DefaultAzureCredential automatically selects the right authentication method for each environment!
from azure.identity import DefaultAzureCredential
# Works everywhere with proper setup
credential = DefaultAzureCredential()
Local Development
🎯 Goal: Fastest, most secure local development
⏱️ Setup: 1 minute
🔐 Security: ⭐⭐⭐⭐⭐ (No secrets in files!)
How It Works
Use your Azure identity for authentication:
# One-time login
az login
# Your credentials stored securely by Azure CLI
# Application uses DefaultAzureCredential → finds az login credentials
Benefits: - ✅ No secrets in files - ✅ Uses your Azure permissions - ✅ Multi-factor authentication support - ✅ Automatic credential refresh - ✅ Audit trail per developer - ✅ Centralized access control
Setup Instructions
1. Install Azure CLI
# macOS
brew install azure-cli
# Ubuntu/Debian
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Windows
winget install Microsoft.AzureCLI
2. Login to Azure
# Login (opens browser)
az login
# Verify you're logged in
az account show
# List available subscriptions
az account list --output table
# Set subscription (if multiple)
az account set --subscription "Your Subscription Name"
3. Verify Permissions
Ensure you have "Cognitive Services OpenAI User" role:
# Check your role assignments
az role assignment list --assignee $(az ad signed-in-user show --query id -o tsv) \
--query "[?roleDefinitionName=='Cognitive Services OpenAI User']" \
--output table
If not assigned, ask your admin to grant access.
4. Configure .env
Edit .env - only these fields needed:
# Azure AI Configuration (REQUIRED)
AZURE_AI_PROJECT_ENDPOINT=https://your-resource.services.ai.azure.com/api/projects/your-project
AZURE_AI_MODEL_DEPLOYMENT_NAME=your-deployment-name
# Authentication (NOT NEEDED - az login handles it!)
# AZURE_TENANT_ID= # Leave blank
# AZURE_CLIENT_ID= # Leave blank
# AZURE_CLIENT_SECRET= # Leave blank
5. Run Application
# Install dependencies
uv sync --prerelease=allow
# Start API
cd apps/api
uv run python -m loan_defenders.api.app
# Authentication automatic via az login!
Troubleshooting
Error: "DefaultAzureCredential failed to retrieve a token"
# Re-login
az login
# Verify
az account show
# Check if subscription is set
az account list --output table
Error: "Access denied" or "403 Forbidden"
# Check your role assignments
az role assignment list --assignee $(az ad signed-in-user show --query id -o tsv)
# Ask admin to grant "Cognitive Services OpenAI User" role
Docker Testing
🎯 Goal: Test full containerized stack locally
⏱️ Setup: 5 minutes
🔐 Security: ⭐⭐⭐⭐ (Secrets in .env, git-ignored)
Why Service Principal?
Docker containers cannot use az login (no browser access). Solution: Service Principal with explicit credentials.
How It Works
Service Principal (App Registration)
├── Client ID (public)
├── Client Secret (private, like a password)
└── Tenant ID (public)
→ Stored in .env (git-ignored)
→ Docker containers use these for authentication
→ DefaultAzureCredential finds them automatically
Setup Instructions
Step 1: Create Service Principal
# Create Service Principal with required role
az ad sp create-for-rbac \
--name "loan-defenders-docker-$(whoami)" \
--role "Cognitive Services OpenAI User" \
--scopes /subscriptions/YOUR_SUBSCRIPTION_ID
# Output (save this!):
{
"appId": "12345678-1234-1234-1234-123456789012", # → AZURE_CLIENT_ID
"password": "abc-DEF_123~xyz", # → AZURE_CLIENT_SECRET
"tenant": "87654321-4321-4321-4321-210987654321" # → AZURE_TENANT_ID
}
Step 2: Add to .env
# Edit .env and add these lines:
AZURE_TENANT_ID=87654321-4321-4321-4321-210987654321
AZURE_CLIENT_ID=12345678-1234-1234-1234-123456789012
AZURE_CLIENT_SECRET=abc-DEF_123~xyz
# Keep existing:
AZURE_AI_PROJECT_ENDPOINT=https://your-resource.services.ai.azure.com/api/projects/your-project
AZURE_AI_MODEL_DEPLOYMENT_NAME=your-deployment-name
Step 3: Verify .env Security
Run Docker Compose
# Build images
docker-compose build
# Start services
docker-compose up -d
# Check logs
docker-compose logs -f api
# Verify authentication working
docker-compose exec api curl http://localhost:8000/health
Security Best Practices
✅ DO:
- Store secrets in .env (git-ignored)
- Rotate secrets every 90 days
- Use unique Service Principal per developer
- Delete Service Principal when leaving team
❌ DON'T:
- Commit .env to git
- Share Service Principal secrets
- Use production credentials for testing
- Store secrets in plaintext files
Secret Rotation
# Rotate secret every 90 days
az ad sp credential reset --id YOUR_CLIENT_ID
# Update .env with new secret
# Restart containers
docker-compose restart
Azure Production
🎯 Goal: Production deployment with best security
⏱️ Setup: Automatic
🔐 Security: ⭐⭐⭐⭐⭐ (No secrets at all!)
How It Works
Managed Identity: Azure automatically provides identity to your Container Apps.
Container App
├── System-Assigned Managed Identity (automatic)
└── RBAC: "Cognitive Services OpenAI User"
→ No secrets needed
→ No configuration needed
→ Azure handles everything automatically
Benefits
✅ No secrets to manage - Azure handles credentials
✅ Automatic rotation - No manual updates
✅ Best security - No credentials can be stolen
✅ RBAC integration - Centralized access control
✅ Audit trail - All access logged automatically
Setup (Automated via Bicep)
When you deploy infrastructure, Bicep automatically:
- Creates Managed Identity for Container App
- Assigns "Cognitive Services OpenAI User" role
- Configures Container App to use identity
- No manual configuration needed!
// Bicep handles this automatically
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
identity: {
type: 'SystemAssigned' // ← Managed Identity created
}
}
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
properties: {
principalId: containerApp.identity.principalId // ← Identity assigned role
roleDefinitionId: cognitiveServicesOpenAIUser // ← "Cognitive Services OpenAI User"
}
}
Application Code
No changes needed! Same code works everywhere:
from azure.identity import DefaultAzureCredential
# In Azure: Uses Managed Identity automatically
# In Docker: Uses Service Principal from env vars
# Locally: Uses az login credentials
credential = DefaultAzureCredential()
client = AzureOpenAIClient(
endpoint=endpoint,
credential=credential # ← Works everywhere!
)
Verification
After deployment:
# Check Container App has identity
az containerapp show \
--name loan-defenders-api \
--resource-group loan-defenders-prod-rg \
--query "identity"
# Check role assignment
az role assignment list \
--assignee $(az containerapp show ... --query "identity.principalId" -o tsv) \
--query "[?roleDefinitionName=='Cognitive Services OpenAI User']"
DefaultAzureCredential Authentication Chain
DefaultAzureCredential tries methods in this order:
1. Environment Variables
├── AZURE_CLIENT_ID
├── AZURE_CLIENT_SECRET
└── AZURE_TENANT_ID
2. Managed Identity (in Azure)
└── System-Assigned or User-Assigned
3. Azure CLI
└── az login credentials
4. Visual Studio Code
└── VS Code Azure extension
5. Azure PowerShell
└── Connect-AzAccount
6. Interactive Browser
└── Last resort - opens browser
This is why one code path works everywhere! 🎯
Comparison Matrix
| Feature | Local Dev | Docker Test | Azure Prod |
|---|---|---|---|
| Method | az login | Service Principal | Managed Identity |
| Setup Time | 1 min | 5 min | Automatic |
| Secrets in Files | ❌ No | ⚠️ Yes (.env) | ❌ No |
| Browser Required | ✅ Once | ❌ No | ❌ No |
| Rotation Needed | ❌ No | ⚠️ Every 90 days | ❌ No |
| Works in Containers | ❌ No | ✅ Yes | ✅ Yes |
| Production Ready | ❌ No | ❌ No | ✅ Yes |
| Security Score | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
Troubleshooting
"DefaultAzureCredential failed to retrieve a token"
Check which environment:
Local Development:
Docker Testing:
# Check .env has credentials
grep AZURE_ .env
# Should see:
# AZURE_TENANT_ID=...
# AZURE_CLIENT_ID=...
# AZURE_CLIENT_SECRET=...
Azure Production:
# Check Managed Identity exists
az containerapp show --name api --resource-group rg --query "identity"
# Check role assignment
az role assignment list --assignee <identity-principal-id>
"Access Denied" or "403 Forbidden"
Local Development:
# Check your role
az role assignment list \
--assignee $(az ad signed-in-user show --query id -o tsv) \
--query "[?roleDefinitionName=='Cognitive Services OpenAI User']"
Docker/Azure:
# Verify Service Principal / Managed Identity has role
az role assignment list \
--assignee <client-id or principal-id> \
--query "[?roleDefinitionName=='Cognitive Services OpenAI User']"
Secrets Exposed in Git
If you accidentally committed .env:
# Remove from git
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "Remove .env from tracking"
# IMPORTANT: Rotate all secrets!
az ad sp credential reset --id YOUR_CLIENT_ID
References
- ADR-036: Azure Authentication Research
- Local Development Guide
- Docker Development Guide
- Azure Deployment Guide
- Microsoft DefaultAzureCredential Docs
Quick Start:
- Developing locally? → Run az login and you're done!
- Testing Docker? → Use # See manual setup below
- Deploying to Azure? → Managed Identity configured automatically!