CloudOpsGuide
terraform

Terraform Azure Service Principal: Authentication Guide

Beginner
12 minutes
October 2026
CloudOpsGuide Team

Terraform Azure Service Principal: Authentication Guide

Create and configure Azure Service Principals for Terraform authentication with step-by-step instructions.

Table of Contents

What is a Service Principal

An Azure Service Principal is an identity created for use with applications, hosted services, and automated tools to access Azure resources. It's like a service account that can be assigned permissions to access resources.

Why Use Service Principals

  • Security: Granular access control
  • Automation: No interactive login required
  • Auditing: Track service activity
  • Multi-tenancy: Work across subscriptions
  • CI/CD Integration: Perfect for automation pipelines

Prerequisites

Required Tools

  • Azure CLI installed
  • Terraform installed (0.12+)
  • Azure subscription with appropriate permissions

Install Azure CLI

# macOS
brew install azure-cli

# Linux
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# Windows
winget install Microsoft.AzureCLI

Login to Azure

az login

Set Default Subscription

# List subscriptions
az account list --output table

# Set default subscription
az account set --subscription "<subscription-id>"

Create Service Principal

Method 1: Using Azure CLI (Recommended)

# Create service principal with Contributor role
az ad sp create-for-rbac --name "terraform-sp" --role "Contributor" --scopes "/subscriptions/<subscription-id>"

# Output will contain:
# {
#   "appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "displayName": "terraform-sp",
#   "password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
#   "tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# }

Save these values securely!

  • appId = Client ID
  • password = Client Secret
  • tenant = Tenant ID

Method 2: With Custom Scope

# Create with scope for specific resource group
az ad sp create-for-rbac \
  --name "terraform-sp" \
  --role "Contributor" \
  --scopes "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>"

Method 3: With Specific Expiration

# Create service principal with 1-year expiration
az ad sp create-for-rbac \
  --name "terraform-sp" \
  --role "Contributor" \
  --years 1

Method 4: Using Azure Portal

  1. Go to Azure Portal → Azure Active Directory
  2. Click App registrations → New registration
  3. Enter name and click Register
  4. Copy Application (client) ID and Directory (tenant) ID
  5. Go to Certificates & secrets → New client secret
  6. Add description and expiration
  7. Copy the Value (client secret)
  8. Go to Subscriptions → Access control (IAM)
  9. Add Role assignment for the service principal

Configure Terraform

Option 1: Environment Variables (Recommended)

export ARM_SUBSCRIPTION_ID="<subscription-id>"
export ARM_CLIENT_ID="<app-id>"
export ARM_CLIENT_SECRET="<client-secret>"
export ARM_TENANT_ID="<tenant-id>"

Add to ~/.bashrc or ~/.zshrc for persistence:

# Azure Terraform Credentials
export ARM_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export ARM_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Option 2: Terraform Provider Block

provider "azurerm" {
  features {}
  
  subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  client_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  client_secret   = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  tenant_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

⚠️ Security Warning: Never commit secrets to Git!

Option 3: Using Azure CLI Authentication

# Login with Azure CLI
az login

# Terraform will automatically use CLI credentials
terraform init
terraform apply

Option 4: Managed Identity (Recommended for Production)

provider "azurerm" {
  features {}
  
  use_msi = true
  subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  tenant_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

Environment Variables

Complete List of Azure Provider Variables

VariableDescriptionRequired
ARM_SUBSCRIPTION_IDAzure Subscription IDYes
ARM_CLIENT_IDService Principal App IDYes*
ARM_CLIENT_SECRETService Principal PasswordYes*
ARM_TENANT_IDAzure Tenant IDYes*
ARM_USE_MSIUse Managed IdentityNo
ARM_CLIENT_CERTIFICATE_PATHPath to client certificateNo
ARM_CLIENT_CERTIFICATE_PASSWORDCertificate passwordNo

*Not required if using Azure CLI or Managed Identity

Example .env File

# .env (never commit this!)
ARM_SUBSCRIPTION_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ARM_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ARM_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ARM_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Load in your shell:

source .env

Security Best Practices

1. Use Least Privilege

# Assign specific role instead of Contributor
az ad sp create-for-rbac \
  --name "terraform-sp" \
  --role "Contributor" \
  --scopes "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>"

2. Rotate Secrets Regularly

# Reset credentials
az ad sp credential reset \
  --name "terraform-sp" \
  --password "new-strong-password"

3. Use Key Vault for Secrets

data "azurerm_key_vault_secret" "client_secret" {
  name         = "terraform-client-secret"
  key_vault_id = azurerm_key_vault.example.id
}

provider "azurerm" {
  features {}
  
  client_id     = var.client_id
  client_secret = data.azurerm_key_vault_secret.client_secret.value
  tenant_id     = var.tenant_id
}

4. Enable Multi-Factor Authentication

For critical service principals, enable conditional access policies.

5. Monitor Service Principal Activity

# Get sign-in logs
az login
az monitor activity-log list \
  --caller "terraform-sp" \
  --max-events 10

6. Use Separate Service Principals

Create different service principals for:

  • Development environment
  • Staging environment
  • Production environment

Troubleshooting

Error: "authentication failed"

Cause: Invalid credentials or expired secret

Solution:

# Verify credentials
az login --service-principal \
  --username "<app-id>" \
  --password "<client-secret>" \
  --tenant "<tenant-id>"

# Reset credentials if needed
az ad sp credential reset --name "terraform-sp"

Error: "insufficient privileges"

Cause: Service principal lacks required permissions

Solution:

# Check assigned roles
az role assignment list \
  --assignee "<app-id>" \
  --output table

# Assign required role
az role assignment create \
  --assignee "<app-id>" \
  --role "Contributor" \
  --scope "/subscriptions/<subscription-id>"

Error: "subscription not found"

Cause: Wrong subscription ID or not accessible

Solution:

# List accessible subscriptions
az account list --output table

# Set correct subscription
az account set --subscription "<subscription-id>"

Error: "tenant ID mismatch"

Cause: Service principal from different tenant

Solution:

# Verify tenant ID
az account show --query tenantId -o tsv

# Update environment variable
export ARM_TENANT_ID="<correct-tenant-id>"

Testing Configuration

Test Authentication

# Test with Azure CLI
az login --service-principal \
  --username "$ARM_CLIENT_ID" \
  --password "$ARM_CLIENT_SECRET" \
  --tenant "$ARM_TENANT_ID"

# Test with Terraform
terraform init
terraform plan

Verify Permissions

# List resource groups accessible
az group list --output table

# Try to create a test resource
az group create --name test-rg --location "UK South"
az group delete --name test-rg --yes --no-wait

Terraform Example

Complete Configuration

# providers.tf
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

# main.tf
resource "azurerm_resource_group" "example" {
  name     = "terraform-example-rg"
  location = "UK South"
}

resource "azurerm_storage_account" "example" {
  name                     = "terraformstorageexample"
  resource_group_name      = azurerm_resource_group.example.name
  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

# outputs.tf
output "resource_group_name" {
  value = azurerm_resource_group.example.name
}

output "storage_account_name" {
  value = azurerm_storage_account.example.name
}

Run Terraform

# Initialize
terraform init

# Plan
terraform plan

# Apply
terraform apply

# Destroy
terraform destroy

Quick Reference

TaskCommand
Create SPaz ad sp create-for-rbac
List SPsaz ad sp list --display-name
Get SP detailsaz ad sp show --id
Reset credentialsaz ad sp credential reset
Assign roleaz role assignment create
List rolesaz role assignment list

Related Articles


Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Beginner
Estimated Reading Time: 12 minutes