Terraform Azure AD Application Management and Entra ID Automation

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.

Sources

  1. oneuptime.com
  2. github.com/hashicorp/terraform-provider-azuread
  3. developer.hashicorp.com

Related Posts