Terraform provides declarative configuration for Azure Active Directory and Microsoft Entra ID resources through the dedicated AzureAD provider. Managing applications, service principals, users, groups, and permissions with Terraform keeps identity configuration version-controlled and consistent across environments. The provider is separate from AzureRM and targets identity objects directly, which makes it suitable for SaaS identity lifecycle automation and IT operations.
Provider Configuration and Version Constraints
The AzureAD provider has specific version requirements and coexists with AzureRM in the same Terraform configuration.
terraform {
required_version = ">= 1.5.0"
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "~> 2.47"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.80"
}
random = {
source = "hashicorp/random"
version = "~> 3.5"
}
}
}
Provider blocks are minimal for Azure AD authentication.
provider "azuread" {}
provider "azurerm" {
features {}
}
Version 1.0 and above of the provider requires Terraform 0.12 or later. A minimal provider configuration example shows:
terraform {
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "~> 2.7.0"
}
}
}
provider "azuread" {
# NOTE: Environment Variables can also be used for Service Principal authentication
# Terraform also supports authenticating via the Azure CLI too.
# See official docs for more info: https://registry.terraform.io/providers/hashicorp/azuread/latest/docs
# client_id = "..."
# client_secret = "..."
# tenant_id = "..."
}
The azuread provider block can be empty because it uses the credentials configured by the Azure CLI for authentication. You can configure other optional provider-specific settings in this block, like client ID, environment, or tenant ID. This provider automatically uses your default tenant ID if you do not set the ARMTENANTID environment variable.
Provider requirements
- Terraform Website
- AzureAD Provider Documentation
- AzureAD Provider Usage Examples
- Learn Tutorial
- Slack Workspace for Contributors
If you're building on Windows, you will also need:
- For GNU32 Make, make sure its bin path is added to your PATH environment variable.
- For Git Bash for Windows, at the step of
Authentication and Data Sources
Data sources allow you to retrieve information from the provider without creating resources.
data "azuread_client_config" "current" {}
Get the current Azure AD tenant details with azureadclientconfig. This is commonly used to set owners for applications and service principals.
Domain retrieval is a common pattern for constructing user principal names.
data "azuread_domains" "example" {
only_initial = true
}
data "azuread_domains" "default" {
only_initial = true
}
This azuread_domains data source retrieves your primary Entra ID tenant domain. Terraform will use this to create user principal names for your users.
Locals capture common values to make configuration easier to read and less repetitive.
locals {
domain_name = data.azuread_domains.default.domains.0.domain_name
users = csvdecode(file("${path.module}/users.csv"))
}
This locals block defines two values:
The domainname local value stores the Entra ID tenant domain name retrieved by the azureaddomains.default data source.
Application Registration Patterns
Application registration is the core of Azure AD automation. A typical setup for a web application that authenticates users is:
resource "azuread_application" "web_app" {
display_name = "Production Web Application"
sign_in_audience = "AzureADMyOrg"
owners = [data.azuread_client_config.current.object_id]
web {
homepage_url = "https://app.example.com"
redirect_uris = [
"https://app.example.com/auth/callback",
"https://app.example.com/auth/silent-callback",
]
implicit_grant {
access_token_issuance_enabled = false
}
}
}
Parameters in this example:
- display_name = "Production Web Application"
- signinaudience = "AzureADMyOrg" for single tenant
- owners = [data.azureadclientconfig.current.object_id]
- homepage_url = "https://app.example.com"
- redirect_uris includes two URIs
A simpler application example:
resource "azuread_application" "example" {
name = "ExampleApp"
}
Application registration supports OAuth settings, API permissions, credentials management, and app roles. Managing these through Terraform keeps your identity configuration version-controlled and consistent across environments.
Service Principals and Consent
Service principals represent applications in tenants. Creating a service principal from an application:
resource "azuread_service_principal" "example" {
application_id = azuread_application.example.application_id
}
Admin consent and app role assignments can be granted through Terraform.
resource "azuread_service_principal" "daemon" {
client_id = azuread_application.daemon.client_id
owners = [data.azuread_client_config.current.object_id]
}
resource "azuread_service_principal" "msgraph" {
client_id = local.microsoft_graph_app_id
use_existing = true
}
resource "azuread_app_role_assignment" "daemon_mail_send" {
app_role_id = azuread_service_principal.msgraph.app_role_ids["Mail.Send"]
principal_object_id = azuread_service_principal.daemon.object_id
resource_object_id = azuread_service_principal.msgraph.object_id
}
resource "azuread_app_role_assignment" "daemon_user_read_all" {
app_role_id = azuread_service_principal.msgraph.app_role_ids["User.Read.All"]
principal_object_id = azuread_service_principal.daemon.object_id
resource_object_id = azuread_service_principal.msgraph.object_id
}
You can grant admin consent for the daemon application using azureadserviceprincipal and azureadapprole_assignment resources.
User and Group Management
User creation uses domain data to build user principal names.
resource "azuread_user" "example" {
user_principal_name = "ExampleUser@${data.azuread_domains.example.domains.0.domain_name}"
display_name = "Example User"
password = "..."
}
Using the for_each meta-argument, you created three unique users using a single resource block. This pattern allows bulk provisioning from a CSV file via locals.
Resource actions are indicated with the following symbols:
- create
Plan output shows:
Plan: 9 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
azuread_group.education: Creating...
azuread_group.managers: Creating...
azuread_group.engineers: Creating...
azuread_group.education: Creation complete after 3s [id=00000000-0000-0000-0000-000000000000]
azuread_group.managers: Creation complete after 3s [id=00000000-0000-0000-0000-000000000000]
azuread_group_member.education["mscott-artistic-coyote"]: Creating...
azuread_group_member.managers["mscott-artistic-coyote"]: Creating...
azuread_group_member.education["jhalpert-artistic-coyote"]: Creating...
azuread_group_member.education["pbeesly-artistic-coyote"]: Creating...
azuread_group.engineers: Creation complete after 3s [id=00000000-0000-0000-0000-000000000000]
azuread_group_member.engineers["jhalpert-artistic-coyote"]: Creating...
azuread_group_member.engineers["pbeesly-artistic-coyote"]: Creating...
azuread_group_member.education["mscott-artistic-coyote"]: Creation complete after 3s [id=00000000-0000-0000-0000-000000000000/member/00000000-0000-0000-0000-000000000000]
azuread_group_member.engineers["jhalpert-artistic-coyote"]: Creation complete after 3s [id=00000000-0000-0000-0000-000000000000/member/00000000-0000-0000-0000-000000000000]
azuread_group_member.managers["mscott-artistic-coyote"]: Creation
Later updates show additional group creation:
Plan: 7 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
azuread_group.customer_success: Creating...
azuread_group_member.engineers["pvance-artistic-coyote"]: Creating...
azuread_group_member.education["dschrute-artistic-coyote"]: Creating...
azuread_group_member.education["kkapoor-artistic-coyote"]: Creating...
azuread_group_member.education["pvance-artistic-coyote"]: Creating...
azuread_group_member.engineers["dschrute-artistic-coyote"]: Creating...
azuread_group_member.engineers["pvance-artistic-coyote"]: Creation complete after 3s [id=00000000-0000-0000-0000-000000000000/member/764e9554-37cb-4382-a9cb-877fbf72516d]
azuread_group_member.education["pvance-artistic-coyote"]: Creation complete after 4s [id=00000000-0000-0000-0000-000000000000/member/764e9554-37cb-4382-a9cb-877fbf72516d]
azuread_group.customer_success: Creation complete after 4s [id=3ed46611-c575-4031-bf85-f95760242cb0]
azuread_group_member.customer_success["kkapoor-artistic-coyote"]: Creating...
azuread_group_member.engineers["dschrute-artistic-coyote"]: Creation complete after 6s [id=00000000-0000-0000-0000-000000000000/member/82cbf25d-1834-449c-857d-91c143514ef9]
azuread_group_member.customer_success["kkapoor-artistic-coyote"]: Creation complete after 3s
Respond yes when prompted to create the Customer Success group and update the group assignments.
State Inspection and Outputs
Use the Terraform state command to list all resources managed by Terraform.
$ terraform state list
data.azuread_domains.default
azuread_user.users["Jim"]
azuread_user.users["Michael"]
azuread_user.users["Pam"]
random_pet.suffix
Retrieve user information:
$ terraform state show 'azuread_user.users["Pam"]'
resource "azuread_user" "users" {
account_enabled = true
business_phones = []
department = "Education"
display_name = "Pam Beesly"
force_password_change = true
id = "REDACTED"
im_addresses = []
job_title = "Engineer"
mail_nickname = "pbeesly"
object_id = "REDACTED"
onpremises_sync_enabled = false
password = (sensitive value)
proxy_addresses = []
show_in_address_list = true
user_principal_name = "[email protected]"
user_type = "Member"
}
Use the Azure CLI to retrieve your newly created users.
Outputs expose sensitive identifiers for downstream consumption.
output "web_app_client_id" {
value = azuread_application.web_app.client_id
description = "Client ID for the web application"
}
output "web_app_client_secret" {
value = azuread_application_password.web_app.value
sensitive = true
description = "Client secret for the web application"
}
output "api_client_id" {
value = azuread_application.api.client_id
description = "Client ID for the API application"
}
output "api_identifier_uri" {
value = azuread_application.api.identifier_uris[0]
description = "Identifier URI for the
Resource attribute comparison
| Resource | Key attributes from reference |
|---|---|
| azureadapplication.webapp | displayname, signinaudience, owners, web.homepageurl, web.redirecturis, web.implicitgrant.accesstokenissuance_enabled |
| azuread_application.example | name |
| azureadserviceprincipal.example | application_id |
| azuread_user.example | userprincipalname, display_name, password |
| azuread_user.users["Pam"] | accountenabled, department, displayname, jobtitle, mailnickname, userprincipalname, user_type |
Provider version matrix
| Provider | Source | Version constraint |
|---|---|---|
| azuread | hashicorp/azuread | ~> 2.47 in versions.tf example; ~> 2.7.0 in minimal example |
| azurerm | hashicorp/azurerm | ~> 3.80 |
| random | hashicorp/random | ~> 3.5 |
Typical web app configuration values
- signinaudience: AzureADMyOrg
- homepage_url: https://app.example.com
- redirect_uris: https://app.example.com/auth/callback, https://app.example.com/auth/silent-callback
- implicitgrant accesstokenissuanceenabled: false
Deployment Workflow and Planning
Terraform configuration consists of blocks of code written in Terraform configuration language. Review each block below to learn what this Terraform configuration defines.
To use the Entra ID provider, you must define a provider block in your configuration.
provider "azuread" {}
Data sources allow you to retrieve information from the provider.
data "azuread_domains" "default" {
only_initial = true
}
This azuread_domains data source retrieves your primary Entra ID tenant domain. Terraform will use this to create user principal names for your users.
The locals block allows you to define values that you reference throughout your configuration. Locals capture common values to make your configuration easier to read and less repetitive.
Further usage documentation is available on the Terraform website.
Conclusion
Terraform Azure AD automation centers on the AzureAD provider, distinct from AzureRM, with explicit version constraints and authentication options. Provider configuration starts with requiredproviders blocks specifying azuread ~> 2.47, azurerm ~> 3.80, and random ~> 3.5 under requiredversion >= 1.5.0. Authentication relies on Azure CLI credentials by default, with optional clientid, clientsecret, and tenant_id settings.
Data sources like azureadclientconfig and azureaddomains supply tenant context for owners and user principal names. Application registration resources define displayname, signinaudience, owners, and web configuration including homepageurl, redirecturis, and implicit grant settings. Service principals link applications to tenants and enable app role assignments for Microsoft Graph permissions such as Mail.Send and User.Read.All.
User and group provisioning uses locals with csvdecode, foreach for bulk creation, and domain data for UPN construction. State inspection via terraform state list and terraform state show provides visibility into created objects with attributes like accountenabled, department, jobtitle, and usertype.
Outputs expose clientid and sensitive clientsecret values for integration. Plan outputs show creation sequences for azureadgroup and azureadgroup_member resources with timestamps and IDs. This workflow supports version-controlled, repeatable Entra ID identity configuration across environments.