☁️ Introduction
Before Terraform can create resources in Azure, it needs to authenticate — to prove who you are.
Once authenticated, Azure checks your permissions (authorization) to determine what you’re allowed to do.
While authenticating via Azure CLI is the easiest method, using an Azure Service Principal (SP) is more secure and ideal for automation, CI/CD pipelines, and multi-user environments.
In this post, we’ll:
✅ Authenticate Terraform using a Service Principal (Client Secret)
✅ Deploy a Virtual Network and Subnet using Terraform
⚙️ Prerequisites
Before you begin, make sure you have the following ready:
✅ Azure subscription
✅ Service Principal details — You can follow the blog post Getting Started with Azure Service Principals.
- Application (client) ID → used as ARM_CLIENT_ID
- Value of Client Secret (copied during app registration) → used as ARM_CLIENT_SECRET
- Directory (tenant) ID → used as ARM_TENANT_ID
- Subscription ID → used as ARM_SUBSCRIPTION_ID
✅ Terraform installed — You can follow the blog post Installing Terraform on Linux (Ubuntu) and Windows (WSL2 with Ubuntu) or Installing Terraform on MacOS.
✅ Azure CLI installed — optional, but helpful for verification
🧩 Step 1: Set Environment Variables
Terraform uses these environment variables to authenticate to Azure securely:
For Linux/macOS/WSL
export ARM_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_CLIENT_SECRET="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
For Windows PowerShell
$env:ARM_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$env:ARM_CLIENT_SECRET="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$env:ARM_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$env:ARM_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
These ensure Terraform authenticates with your Service Principal credentials instead of your personal Azure login.
⚙️ Optional Pre-check: Confirm Terraform Uses the Service Principal
By default, Terraform supports multiple authentication methods for Azure and follows a specific order of precedence:
1️⃣ Environment Variables (ARM_CLIENT_ID, ARM_CLIENT_SECRET, etc.)
2️⃣ Managed Identity (MSI) (used in Azure-hosted environments)
3️⃣ Azure CLI Login (az login)
4️⃣ Azure PowerShell Login
That means — if you’ve already set the environment variables and are logged in with Azure CLI, Terraform will automatically use your credentials from the environment variables.
However, to avoid confusion and ensure Terraform is indeed using your Service Principal credentials, we’ll log out of Azure CLI before running Terraform commands.
✅ Log out from Azure CLI
az logout
Now continue with the Terraform commands in further steps. If Terraform still runs successfully after logging out of Azure CLI, it confirms that it’s using your Service Principal for authentication.
Step 2: Terraform Script (main.tf)
Create a new folder (e.g., terraform-azure-sp) and inside it, create a file named main.tf.
mkdir terraform-azure-vm
cd terraform-azure-vm
nano main.tf
Create a file named main.tf.
nano main.tf
Alternatively if you are familiar with the vi editor, you may create file as vi main.tf. Personally I use vi.
Paste the following Terraform configuration and save file:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
resource_provider_registrations = "none" # This is only required when the User, Service Principal, or Identity running Terraform lacks the permissions to register Azure Resource Providers.
features {}
#Uncomment the line and hardcode subscription id if you are not using ARM_SUBSCRIPTION_ID environment variable in next step
#subscription_id = "your-subscription-id"
}
# Option 1: Create a new Resource Group
resource "azurerm_resource_group" "rg" {
name = "tfcli-rg"
location = "eastus"
}
# Option 2: Use an existing Resource Group (if provided)
# data "azurerm_resource_group" "rg" {
# name = "student-assigned-rg-name"
# }
# Create a Virtual Network
resource "azurerm_virtual_network" "vnet" {
name = "tfcli-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
# Create a Subnet inside the Virtual Network
resource "azurerm_subnet" "subnet" {
name = "tfcli-subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
}
💡Note 1: If your Resource Group was provided to you (for example, “rg-student-john”), simply comment out the resource block in option 1 and uncomment the data block in option 2.
💡Note 2: Make sure the address_space and address_prefixes are not in use already.
Step 3: Initialize and Apply Terraform
1️⃣ Initialize Terraform
Run the following command from within the project folder where main.tf is present.
terraform init
Downloads the Azure provider plugin and prepares Terraform for this project.
💡 You’ll see “Terraform has been successfully initialized.” when ready.
2️⃣ Review the Plan
Run the following command from within the project folder where main.tf is present.
terraform plan
Previews what Terraform will create — nothing is applied yet.
💡 Look for “Plan: 3 to add, 0 to change, 0 to destroy.”
💡 Terraform may show a note suggesting the -out option (e.g., “You didn’t use the -out option…”). You can safely ignore it for this lab, but in production environments, use terraform plan -out=tfplan and then terraform apply tfplan for consistency.
3️⃣ Apply the Configuration
terraform apply
Terraform connects to Azure using your Azure CLI login and starts creating the resources. It will show us the plan again and a prompt to confirm change.
When prompted, type yes and press Enter.
💡 After a short while, you should see:
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Step 4: Verify
Go to the Azure Portal → Resource Groups → tfcli-rg,
and confirm that the Virtual Network (tfcli-vnet) was created successfully.
Then, open the Virtual Network and check inside it to confirm that the Subnet (tfcli-subnet) was also created successfully.
💡 If your Resource Group name differs, open the one assigned to you instead.
Step 5: (Optional) Clean Up
When you’re done, clean up resources to avoid charges:
terraform destroy
Removes all resources we just created.
When prompted, type yes to confirm.
💡 You’ll see “Destroy complete! Resources: 2 destroyed.” — keeping your Azure environment clean.
Bonus: Verify Terraform Sign-ins in Azure
You can confirm that Terraform is authenticating via your Service Principal by checking the Azure portal:
- Go to Azure Portal → Entra ID (Azure Active Directory) → Monitoring → Sign-ins
- Look for entries under your Service Principal name.

Summary
- Use a Service Principal for secure, repeatable automation
- Set environment variables instead of hardcoding credentials
- Run Terraform init → plan → apply → destroy
- Verify authentication by logging out of Azure CLI or checking sign-ins in Azure
By following this process, you ensure that Terraform connects to Azure securely and predictably through your Service Principal — a must for professional DevOps and CI/CD environments.
🎁 Bonus 1: Authenticating via Service Principal (Certificate)
Instead of using a Client Secret, you can also authenticate Terraform with a certificate-based Service Principal.
This approach is more secure because it eliminates shared secrets and reduces the risk of credential exposure.
Here’s how it works:
- You create or upload an X.509 certificate (public/private key pair) in Azure AD when registering your application.
- The private key stays on your local machine or secure CI/CD environment, and the public key is registered with the Service Principal.
- Terraform uses this certificate to authenticate silently with Azure — no client secret required.
🧩 Example Environment Variables
Set these before running Terraform:
export ARM_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_CLIENT_CERTIFICATE_PATH="/path/to/certificate.pem"
export ARM_CLIENT_CERTIFICATE_PASSWORD="your-cert-password-if-any"
💡 Tip:
Use certificate-based authentication when:
- Security policies prohibit storing client secrets.
- You’re automating deployments in enterprise or regulated environments.
👉 Read the detailed guide here:
Authenticating Terraform via Service Principal (Certificate) — coming soon
🎁 Bonus 2: Managed Identities — A Simpler Alternative
If you’re running Terraform scripts from Azure resources like Virtual Machines, App Services, or Function Apps, you might not need a Service Principal at all.
Azure offers Managed Identities, which let resources authenticate to Azure services without any client secrets or credentials.
This means:
- ✅ No manual secret rotation
- ✅ No credential leaks
- ✅ No configuration hassles
In simple terms — Managed Identities are like Service Principals that Azure manages for you automatically.
👉 To understand how they work and when to prefer them, read the full post here:
Authenticating Terraform using managed identities (coming soon)
🎁 Bonus 3: Running Terraform from Azure DevOps Pipelines using Service Connections
When executing Terraform scripts from Azure DevOps, authentication works a little differently — you usually won’t log in manually or set credentials locally.
Instead, you can connect Azure DevOps to Azure using a Service Connection, which securely links your DevOps project to your Azure subscription.
Here’s how it works:
- The Service Connection is typically based on a Service Principal created in Azure AD.
- You configure it once under Project Settings → Service Connections → Azure Resource Manager.
- Azure DevOps then injects the required credentials into the pipeline automatically during execution.
💡 Best Practice:
- Use a dedicated Service Principal for CI/CD with limited permissions (e.g., Contributor on a specific Resource Group).
- Avoid storing secrets directly in pipeline variables — use Azure Key Vault integration instead.
- Combine with Terraform remote state (e.g., in Azure Storage) to ensure collaboration and consistency.
👉 To learn step-by-step how to configure this, check out:
Authenticating Terraform from Azure DevOps Pipelines (coming soon)
🎁 Bonus 4: Authenticating via OIDC (for GitHub Actions / Azure DevOps Pipelines)
In modern CI/CD pipelines like GitHub Actions and Azure DevOps, you can use OpenID Connect (OIDC) to let Terraform authenticate to Azure without storing any secrets or certificates.
With OIDC, Azure trusts the identity of your workflow or pipeline and issues a short-lived access token only for that specific run. This makes it one of the most secure and scalable authentication options for Terraform automation.
💡 How it works:
- Your pipeline (GitHub or Azure DevOps) requests an OIDC token when the workflow starts.
- Azure AD (now Entra ID) validates this token using a Federated Identity Credential you configure in your Azure App Registration.
- Once validated, Terraform can access Azure resources using temporary credentials — no client secret or certificate needed.
✅ Benefits:
- Secretless and fully automated authentication
- No need for secret rotation or storage
- Ideal for secure, large-scale CI/CD automation
👉 To learn how to configure this step-by-step, read the detailed guide:
Authenticating Terraform via OIDC (GitHub Actions & Azure DevOps) — coming soon