Notes from the field: Azure Active Directory B2C with Terraform
Streamline User and Account Management in Azure AD B2C with Terraform Automation
Introduction
Azure Active Directory B2C provides business-to-customer identity as a service. Your customers can use their preferred social, enterprise, or local account identities to get single sign-on access to your applications and APIs.
As you see above in the quote from the Documentation site, B2C is a directory for your customers services consumer accounts.
In one of our recent projects, we received a request to automate some of the B2C settings using Terraform for a fully automated setup.
What we needed to automate?
We need to set up a User Flow (find more about User Flows here) for every new rollout of the service.
Additionally for every new rollout we had to create some users with the account type "Local account" and Email sign-in as a login option.
How we started?
At the beginning we wanted to use the Azure AD Terraform provider because most of the features of B2C are in the Azure AD Provider, as the backend is nearly identical between the services even if there are some major differences. It quickly became apparent, that both of the requests from above were not possible with the Azure AD provider, so we quickly switched to direct REST requests via the Microsoft Graph API as you find in the dedicated documentation page for B2C Graph API.
Dive into the Graph API for B2C
As written above, we quickly realized, that we have to do the needed configurations via Graph requests. For Graph API we always have to start with authentication and so let's start how we are getting the token for the requests.
Get Graph API Bearer Token with Terraform http Provider
For connecting to Graph API we need a Bearer Token. The easiest way we have found, is using the Terraform http Provider.
To get an application token we need an App registration inside of the B2C tenant with the corresponding API permissions. These are:
IdentityUserFlow.ReadWrite.All - to create the User Flows
User.ReadWrite.All - to create the B2C consumer users later
Find the screenshots of how I configured my app registration (service principal) for my code below. For simplicity reasons for this blog post, I have created a client secret to use in my Terraform code.
After creating the app registration including the Secret we are ready to get an Bearer Token. Find my example code below:
variable "b2c_tenant_id" {
type = string
description = "Tenant ID of the Azure AD B2C Tenant."
}
variable "b2c_client_id" {
type = string
description = "Client/Application ID of the Azure AD B2C Service Principal."
}
variable "b2c_client_secret" {
type = string
description = "Client Secret of the Azure AD B2C Service Principal."
}
data "http" "b2c_get_access_token" {
url = "https://login.microsoftonline.com/${var.b2c_tenant_id}/oauth2/v2.0/token"
method = "POST"
request_headers = {
"Content-Type" = "application/x-www-form-urlencoded"
}
request_body = "client_id=${var.b2c_client_id}&client_secret=${var.b2c_client_secret}&grant_type=client_credentials&scope=https://graph.microsoft.com/.default"
}
We are using three variables here, which we have gotten from the app-registration Overview page and the secret value for the Client secret.
Set up the Rest API Provider with our token
For our use-case we found that the restapi provider from Mastercard fits best for our needs.
Before starting with our requests we have to set up the provider with our Bearer token which we created before. This could happen directly in the provider block:
provider "restapi" {
uri = "https://graph.microsoft.com"
write_returns_object = true
debug = false
headers = {
"Authorization" = "Bearer ${jsondecode(data.http.b2c_get_access_token.response_body)["access_token"]}",
"Content-Type" = "application/json"
}
}
We are grabbing the "access_token" from our data resource and decode the JSON response we have gotten.
Creating the B2C User Flow
We are using a builtin User Flow of type "signIn". You could find the Graph API documentation for creation of the flow here, but there are some properties, which are available but not document as you will see in my example.
resource "restapi_object" "create_b2c_user_flow" {
path = "/beta/identity/b2cUserFlows"
create_method = "POST"
destroy_method = "DELETE"
destroy_path = "/beta/identity/b2cUserFlows/B2C_1_${var.environment}-signin"
data = jsonencode(
{
"id" : "B2C_1_${var.environment}-signin",
"userFlowType" : "signIn",
"userFlowTypeVersion" : 3,
"isConditionalAccessEnforced" : true,
"singleSignOnSessionConfiguration" : {
"enforceIdTokenHintOnLogout" : true,
"isKeepMeSignedInEnabled" : false,
"isSessionLifetimeAbsolute" : false,
"sessionLifetimeInMinutes" : 1440,
"sessionScope" : "tenant"
},
"passwordComplexityConfiguration" : {
"allowExpiredPasswordReset" : true,
"complexityLevel" : "strong",
"isSelfServicePasswordResetAllowed" : true
}
}
)
}
The best way to describe the properties is to mark them in the GUI, where you could see all of them but nowhere in the API documentation.
After successful creation and automation of the User flow creation we are now going to the next requirement.
Creation of some local users inside of B2C
As you know B2C is a stripped-down version of Entra ID (former Azure AD) and so not all features which exist in B2C are available in the Azure AD Terraform provider. One of these features is to create users with the Account Type "Local Account" so B2C users with E-Mail sign-in. So we will again use our trusted Rest API with the Mastercard Rest API Provider.
We are using the same Provider configuration as above with our existing Bearer Token to create the users in the example code below.
resource "random_password" "admin_password" {
length = 14
special = true
override_special = "%!#()$@§"
}
resource "restapi_object" "create_master_admin_accounts" {
path = "/v1.0/users"
create_method = "POST"
destroy_method = "DELETE"
data = jsonencode(
{
"displayName" : "admin1",
"identities" : [
{
"signInType" : "emailAddress",
"issuer" : "${var.b2c_tenant_name}.onmicrosoft.com",
"issuerAssignedId" : "admin1@domain.com"
}
],
"passwordProfile" : {
"password" : random_password.admin_password.result,
"forceChangePasswordNextSignIn" : false
},
"passwordPolicies" : "DisablePasswordExpiration"
}
)
}
At first we are creating a random password 14 characters long to use it later inside of the user creation.
As you see in the code, we are going to the "normal" Rest API /users endpoint but the important difference is under the identities block, where we configure the Local Account and set a password to the "random_password" result.
More examples and possibilities are available in the documentation of the API.
Conclusion
In this exploration of Azure Active Directory B2C with Terraform, we've outlined a comprehensive approach to automating user management and authentication processes. By leveraging the power of the Microsoft Graph API in conjunction with Terraform, we've demonstrated that even complex identity solutions can be efficiently managed and scaled. The practical examples provided should serve as a solid foundation for your projects, enabling you to customize and extend your identity capabilities as needed.
Should you encounter any challenges or have questions about implementing these solutions in your own environments, feel free to connect with me on X (Twitter) or LinkedIn. Also if you have some feedback/ideas for future ideas please connect with me.
Don't forget to share this article with peers who might find it helpful in their professional journey.