Skip to main content

Azure Authentication

This guide shows you how to configure Azure authentication using the native atmos auth system.

Overview

Atmos Auth provides native Azure authentication that works identically to az login, with support for:

  • Device Code Flow (browser-based authentication for users)
  • OIDC (workload identity for CI/CD pipelines)
  • Service Principals (client credentials for automation)
  • Sovereign Clouds (Azure Government / GCC High, Azure China / Mooncake)

All authentication methods write credentials to the Azure CLI MSAL cache (~/.azure/msal_token_cache.json), ensuring full compatibility with Terraform's Azure providers (azurerm, azuread, azapi).

Quick Start

Basic Azure Device Code Authentication

The simplest Azure authentication uses device code flow for interactive user login:

auth:
providers:
azure-dev:
kind: azure/device-code
spec:
tenant_id: "12345678-1234-1234-1234-123456789012"
subscription_id: "87654321-4321-4321-4321-210987654321"
location: "eastus"

identities:
azure-dev-subscription:
default: true
kind: azure/subscription
via:
provider: azure-dev
principal:
subscription_id: "87654321-4321-4321-4321-210987654321"
location: "eastus"

Authenticate:

atmos auth login

This will:

  1. Display a device code and verification URL
  2. Open your browser to https://microsoft.com/devicelogin
  3. Prompt you to enter the code and sign in
  4. Cache credentials for all Azure providers (azurerm, azuread, azapi)

Authentication Methods

Device Code Flow (Interactive)

Best for: Developer workstations, interactive sessions

Device code flow provides browser-based authentication with full MFA support:

auth:
providers:
azure-interactive:
kind: azure/device-code
spec:
tenant_id: "YOUR_TENANT_ID"
subscription_id: "YOUR_SUBSCRIPTION_ID"
location: "eastus"

identities:
azure-dev:
kind: azure/subscription
via:
provider: azure-interactive
principal:
subscription_id: "YOUR_SUBSCRIPTION_ID"
location: "eastus"

How it works:

  1. Atmos displays a device code (e.g., "WDDD-HRQV")
  2. Browser opens to Microsoft's device login page
  3. You enter the code and sign in (with MFA if configured)
  4. Atmos receives 3 token scopes (shown here for Azure Commercial; sovereign clouds use their cloud-specific scopes automatically):
    • https://management.azure.com/.default (Azure Resource Manager)
    • https://graph.microsoft.com/.default (Azure AD operations)
    • https://vault.azure.net/.default (Azure KeyVault operations)

Usage:

# Authenticate interactively
atmos auth login --identity azure-dev

# Use with Terraform
atmos terraform plan my-component -s my-stack

OIDC (Workload Identity for CI/CD)

Best for: GitHub Actions, GitLab CI, Azure DevOps pipelines

OIDC enables passwordless authentication using federated credentials:

auth:
providers:
azure-oidc:
kind: azure/oidc
spec:
tenant_id: "YOUR_TENANT_ID"
client_id: "YOUR_APP_CLIENT_ID"
subscription_id: "YOUR_SUBSCRIPTION_ID"
location: "eastus"

identities:
azure-ci:
kind: azure/subscription
via:
provider: azure-oidc
principal:
subscription_id: "YOUR_SUBSCRIPTION_ID"

Setup Requirements:

  1. Create an Azure AD App Registration
  2. Configure federated credentials for your CI/CD platform
  3. Grant app appropriate Azure RBAC roles
  4. Set environment variables in your pipeline:
    AZURE_CLIENT_ID="YOUR_APP_CLIENT_ID"
    AZURE_TENANT_ID="YOUR_TENANT_ID"
    AZURE_SUBSCRIPTION_ID="YOUR_SUBSCRIPTION_ID"

Example GitHub Actions workflow:

name: Deploy Infrastructure
on:
push:
branches: [main]

permissions:
id-token: write # Required for OIDC token
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Atmos
uses: cloudposse/github-action-setup-atmos@v2

- name: Authenticate to Azure
run: atmos auth login --identity azure-ci
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Deploy
run: atmos terraform apply my-component -s prod

Service Principal (Client Credentials)

Best for: Automation, service accounts, long-running processes

Service principals use client ID and secret for non-interactive authentication:

auth:
providers:
azure-service-principal:
kind: azure/service-principal
spec:
tenant_id: "YOUR_TENANT_ID"
client_id: "YOUR_SERVICE_PRINCIPAL_CLIENT_ID"
client_secret: "YOUR_CLIENT_SECRET" # Store securely!
subscription_id: "YOUR_SUBSCRIPTION_ID"
location: "eastus"

identities:
azure-automation:
kind: azure/subscription
via:
provider: azure-service-principal
principal:
subscription_id: "YOUR_SUBSCRIPTION_ID"

Security Best Practices:

  • Never commit secrets to version control
  • Use environment variables: export AZURE_CLIENT_SECRET="..."
  • Or use Atmos's secrets integration:
    client_secret: "{{ (store.get \"secret/azure/client-secret\").data.value }}"

Usage:

# Set secret via environment variable
export AZURE_CLIENT_SECRET="your-secret-here"

# Authenticate
atmos auth login --identity azure-automation

# Run Terraform
atmos terraform apply my-component -s prod

Multi-Subscription Workflows

Atmos makes it easy to work with multiple Azure subscriptions:

auth:
providers:
azure-main:
kind: azure/device-code
spec:
tenant_id: "YOUR_TENANT_ID"
subscription_id: "DEV_SUBSCRIPTION_ID"
location: "eastus"

identities:
azure-dev:
kind: azure/subscription
via:
provider: azure-main
principal:
subscription_id: "DEV_SUBSCRIPTION_ID"
location: "eastus"

azure-staging:
kind: azure/subscription
via:
provider: azure-main
principal:
subscription_id: "STAGING_SUBSCRIPTION_ID"
location: "westus"

azure-prod:
kind: azure/subscription
via:
provider: azure-main
principal:
subscription_id: "PROD_SUBSCRIPTION_ID"
location: "eastus2"

Switch between subscriptions:

# Deploy to dev
atmos terraform apply my-component -s dev --identity azure-dev

# Deploy to staging
atmos terraform apply my-component -s staging --identity azure-staging

# Deploy to prod
atmos terraform apply my-component -s prod --identity azure-prod

Terraform Provider Compatibility

Atmos Azure authentication works seamlessly with all Terraform Azure providers:

azurerm Provider

Atmos sets ARM_USE_CLI=true, enabling the azurerm provider to use Azure CLI credentials:

provider "azurerm" {
features {}
# Atmos automatically sets:
# - ARM_USE_CLI=true
# - ARM_SUBSCRIPTION_ID
# - ARM_TENANT_ID
# - ARM_LOCATION
}

resource "azurerm_key_vault" "example" {
name = "my-keyvault"
location = "eastus"
tenant_id = data.azurerm_client_config.current.tenant_id
# KeyVault operations work because Atmos provides KeyVault token scope
}

azuread Provider

The azuread provider automatically uses Graph API tokens from Atmos:

provider "azuread" {
# Atmos provides Graph API token via ARM_USE_CLI
}

resource "azuread_group" "example" {
display_name = "My Group"
security_enabled = true
}

azapi Provider

The azapi provider also works with Azure CLI authentication:

provider "azapi" {
# Atmos authentication "just works"
}

resource "azapi_resource" "example" {
type = "Microsoft.Network/virtualNetworks@2021-02-01"
name = "my-vnet"
parent_id = azurerm_resource_group.example.id
# ...
}

Token Scopes

Atmos Azure authentication provides all three token scopes required for full Azure functionality:

Azure Commercial (public):

Token ScopeUsed ByPurpose
https://management.azure.com/.defaultazurerm, azapiAzure Resource Manager operations
https://graph.microsoft.com/.defaultazureadAzure Active Directory operations
https://vault.azure.net/.defaultazurermAzure KeyVault operations

Azure Government (GCC High):

Token ScopeUsed ByPurpose
https://management.usgovcloudapi.net/.defaultazurerm, azapiAzure Resource Manager operations
https://graph.microsoft.us/.defaultazureadAzure Active Directory operations
https://vault.usgovcloudapi.net/.defaultazurermAzure KeyVault operations

Azure China (Mooncake):

Token ScopeUsed ByPurpose
https://management.chinacloudapi.cn/.defaultazurerm, azapiAzure Resource Manager operations
https://microsoftgraph.chinacloudapi.cn/.defaultazureadAzure Active Directory operations
https://vault.azure.cn/.defaultazurermAzure KeyVault operations

Atmos automatically selects the correct scopes based on the cloud_environment setting in your provider configuration. This matches exactly what az login provides, ensuring 100% compatibility.

Authentication Flow

When you run atmos auth login:

  1. Provider Authentication: Atmos authenticates using configured method (device code/OIDC/service principal)
  2. Token Acquisition: Requests all 3 token scopes from Azure AD
  3. MSAL Cache Update: Writes tokens to ~/.azure/msal_token_cache.json
  4. Profile Update: Updates ~/.azure/azureProfile.json with subscription info
  5. Environment Setup: Sets ARM_* environment variables for Terraform
  6. Credential Storage: Caches credentials in system keyring for reuse

Common Patterns

Pattern 1: Developer + CI/CD

Use device code for developers, OIDC for pipelines:

auth:
providers:
azure-dev-interactive:
kind: azure/device-code
spec:
tenant_id: "YOUR_TENANT_ID"
subscription_id: "DEV_SUBSCRIPTION_ID"

azure-ci:
kind: azure/oidc
spec:
tenant_id: "YOUR_TENANT_ID"
client_id: "YOUR_APP_CLIENT_ID"
subscription_id: "PROD_SUBSCRIPTION_ID"

identities:
azure-dev:
default: true # Default for developers
kind: azure/subscription
via:
provider: azure-dev-interactive
principal:
subscription_id: "DEV_SUBSCRIPTION_ID"

azure-prod-ci:
kind: azure/subscription
via:
provider: azure-ci
principal:
subscription_id: "PROD_SUBSCRIPTION_ID"

Pattern 2: Multi-Region Deployment

Deploy to multiple Azure regions:

auth:
providers:
azure-main:
kind: azure/device-code
spec:
tenant_id: "YOUR_TENANT_ID"
subscription_id: "YOUR_SUBSCRIPTION_ID"

identities:
azure-eastus:
kind: azure/subscription
via:
provider: azure-main
principal:
subscription_id: "YOUR_SUBSCRIPTION_ID"
location: "eastus"

azure-westus:
kind: azure/subscription
via:
provider: azure-main
principal:
subscription_id: "YOUR_SUBSCRIPTION_ID"
location: "westus"

azure-westeurope:
kind: azure/subscription
via:
provider: azure-main
principal:
subscription_id: "YOUR_SUBSCRIPTION_ID"
location: "westeurope"

Sovereign Cloud Support (GCC High, China)

Azure sovereign clouds (US Government / GCC High, China / Mooncake) use different authentication endpoints, API scopes, and storage URLs than Azure Commercial. Atmos supports Azure Commercial (public), Azure Government (usgovernment), and Azure China (china) through the cloud_environment provider setting.

Azure Government (GCC High)

For organizations operating in Azure Government (including GCC High and DoD regions):

auth:
providers:
azure-gov:
kind: azure/device-code
spec:
tenant_id: "YOUR_GOV_TENANT_ID"
subscription_id: "YOUR_GOV_SUBSCRIPTION_ID"
location: "usgovvirginia"
cloud_environment: usgovernment

identities:
azure-gov-sub:
kind: azure/subscription
via:
provider: azure-gov
principal:
subscription_id: "YOUR_GOV_SUBSCRIPTION_ID"
location: "usgovvirginia"

Key differences from commercial:

  • Login endpoint: login.microsoftonline.us (instead of login.microsoftonline.com)
  • Portal: https://portal.azure.us/
  • Blob storage: blob.core.usgovcloudapi.net

Azure China (Mooncake)

For organizations operating in Azure China:

auth:
providers:
azure-china:
kind: azure/device-code
spec:
tenant_id: "YOUR_CHINA_TENANT_ID"
subscription_id: "YOUR_CHINA_SUBSCRIPTION_ID"
location: "chinaeast2"
cloud_environment: china

identities:
azure-china-sub:
kind: azure/subscription
via:
provider: azure-china
principal:
subscription_id: "YOUR_CHINA_SUBSCRIPTION_ID"
location: "chinaeast2"

Key differences from commercial:

  • Login endpoint: login.chinacloudapi.cn
  • Portal: https://portal.azure.cn/
  • Blob storage: blob.core.chinacloudapi.cn

GCC High OIDC for CI/CD

For GitHub Actions workflows targeting Azure Government:

auth:
providers:
azure-gov-oidc:
kind: azure/oidc
spec:
tenant_id: "YOUR_GOV_TENANT_ID"
client_id: "YOUR_GOV_APP_CLIENT_ID"
subscription_id: "YOUR_GOV_SUBSCRIPTION_ID"
cloud_environment: usgovernment

identities:
azure-gov-sub:
kind: azure/subscription
via:
provider: azure-gov-oidc
principal:
subscription_id: "YOUR_GOV_SUBSCRIPTION_ID"
location: "usgovvirginia"
# .github/workflows/deploy-gov.yaml
jobs:
deploy:
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Deploy to Azure Government
run: atmos terraform apply mycomponent -s prod
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_GOV_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_GOV_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_GOV_SUBSCRIPTION_ID }}

Terraform Backend with Sovereign Clouds

When using !terraform.state YAML functions with sovereign clouds, the Terraform azurerm backend environment field must be set:

# Stack configuration
components:
terraform:
vpc:
backend:
azurerm:
storage_account_name: govstorageaccount
container_name: tfstate
key: terraform.tfstate
environment: usgovernment # Required for sovereign clouds

Atmos uses this environment field to resolve the correct blob storage endpoint when reading Terraform state files. If the same stack also uses Atmos auth, keep spec.cloud_environment and backend.azurerm.environment set to the same cloud value so authentication endpoints and Terraform state URLs stay aligned. Additionally, when cloud_environment is set to a sovereign cloud, Atmos exports ARM_ENVIRONMENT and AZURE_ENVIRONMENT to the subprocess environment, which the Terraform azurerm provider uses to select the correct Azure Resource Manager endpoints.

Supported Cloud Environments

The cloud_environment setting applies to all Azure provider kinds: azure/device-code, azure/oidc, and azure/cli.

cloud_environmentCloudLogin EndpointBlob StoragePortal
public (default)Commerciallogin.microsoftonline.comblob.core.windows.netportal.azure.com
usgovernmentUS Government / GCC Highlogin.microsoftonline.usblob.core.usgovcloudapi.netportal.azure.us
chinaChina (Mooncake)login.chinacloudapi.cnblob.core.chinacloudapi.cnportal.azure.cn

Troubleshooting

Token Expiration

Azure tokens typically expire after 1 hour. Atmos automatically caches and reuses valid tokens:

# Check current authentication status
atmos auth whoami

# Re-authenticate if expired
atmos auth login

Missing Token Scopes

If you see errors about missing permissions, ensure all 3 token scopes are configured:

# Check MSAL cache contents
cat ~/.azure/msal_token_cache.json | jq '.AccessToken | keys[]'

The expected scope entries depend on your cloud environment:

ScopeCommercial (public)US Government (usgovernment)China (china)
Managementmanagement.azure.commanagement.usgovcloudapi.netmanagement.chinacloudapi.cn
Graph APIgraph.microsoft.comgraph.microsoft.usmicrosoftgraph.chinacloudapi.cn
Key Vaultvault.azure.netvault.usgovcloudapi.netvault.azure.cn

Provider Not Found

Ensure your Terraform providers are configured to use ARM_USE_CLI:

# Check environment
atmos auth env --identity azure-dev

# Should show:
# ARM_USE_CLI=true
# ARM_SUBSCRIPTION_ID=...
# ARM_TENANT_ID=...

Security Considerations

Credential Storage

  • Credentials are stored in your system keyring (Keychain on macOS, Secret Service on Linux, Credential Manager on Windows)
  • Tokens are also cached in ~/.azure/msal_token_cache.json for Terraform provider compatibility
  • Both storages are secured by your operating system

Least Privilege

Grant identities only the permissions they need:

# Example: Grant Contributor role to a subscription
az role assignment create \
--assignee YOUR_APP_CLIENT_ID \
--role Contributor \
--scope /subscriptions/YOUR_SUBSCRIPTION_ID

Audit Logging

Enable Azure AD sign-in logs to audit authentication:

  • Go to Azure Portal → Azure Active Directory → Monitoring → Sign-in logs
  • Filter by "Application" to see Atmos authentications

Migration from az login

Already using az login? Atmos authentication is a drop-in replacement:

Before (using az login):

az login
az account set --subscription "YOUR_SUBSCRIPTION_ID"
terraform apply

After (using Atmos):

atmos auth login --identity azure-dev
atmos terraform apply my-component -s my-stack

Both write to the same Azure CLI credential files, so your Terraform code doesn't need any changes.

Next Steps