Understanding MS Graph Application Details in the AzureAD Terraform Provider
When configuring authentication and access permissions in Azure environments, particularly with well-known applications like Microsoft Graph, it’s crucial to understand how to retrieve and apply the right identifiers — whether they’re roles or OAuth scopes. This clarity becomes especially important when using infrastructure-as-code tools like Terraform and working with the AzureAD provider.
Accessing Microsoft Graph Details
To begin, we can reference Microsoft Graph’s application details using Terraform’s azuread_application_published_app_ids data source. This helps identify well-known application IDs which are essential for configuring authentication and authorization.
data "azuread_application_published_app_ids" "well_known" {}
data "azuread_service_principal" "msgraph" {
client_id = data.azuread_application_published_app_ids.well_known.result["MicrosoftGraph"]
}
In this example, we fetch the Microsoft Graph service principal using its known application ID. This is foundational when you’re setting up custom permissions, like giving an Azure API Management (APIM) instance access to Entra ID to retrieve user profile data.
Roles vs. OAuth Scopes in Microsoft Graph
While configuring access, you may notice that not all permissions behave the same way. When granting access through the Azure Portal (ClickOps), the permissions often appear as OAuth scopes. However, permissions such as the ability for an application to manage groups or other apps typically come in the form of roles.

Understanding the distinction is critical:
- Roles (also referred to as app roles) are used for application-level permissions and are typically consented to by administrators.
- OAuth Scopes (OAuth2 permission scopes) are used for delegated user permissions and can also be consented to by users.
Exploring App Roles
To explore all the roles associated with the Microsoft Graph service principal, you can output them using the app_role_idsproperty. For easier inspection, you might write these values to a local file:
resource "local_file" "role_ids" {
content = jsonencode(data.azuread_service_principal.msgraph.app_role_ids)
filename = "roles.json"
}
A sample output might look like:
{
"Application-RemoteDesktopConfig.ReadWrite.All": "3be0012a-cc4e-426b-895b-f9c836bf6381",
"Application.Read.All": "9a5d68dd-52b0-4cc2-bd40-abcf44ac3a30",
"Application.ReadUpdate.All": "fc023787-fd04-4e44-9bc7-d454f00c0f0a",
"Application.ReadWrite.All": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9",
"Application.ReadWrite.OwnedBy": "18a4783c-866b-4cc7-a460-3d5e5662c884"
}
If you want more than just the IDs, the app_roles output provides full metadata including descriptions, display names, and the types of principals that can use them:
[
{
"allowed_member_types": [
"Application"
],
"description": "Allows the app to create, read, update and delete applications and service principals without a signed-in user. Does not allow management of consent grants.",
"display_name": "Read and write all applications",
"enabled": true,
"id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9",
"value": "Application.ReadWrite.All"
},
{
"allowed_member_types": [
"Application"
],
"description": "Allows the app to create other applications, and fully manage those applications (read, update, update application secrets and delete), without a signed-in user. It cannot update any apps that it is not an owner of.",
"display_name": "Manage apps that this app creates or owns",
"enabled": true,
"id": "18a4783c-866b-4cc7-a460-3d5e5662c884",
"value": "Application.ReadWrite.OwnedBy"
}
]
This detailed view is useful for understanding what each role actually does, beyond just plugging in an ID.
Exploring OAuth2 Permission Scopes
In contrast, permissions granted via ClickOps — such as allowing Azure API Management to read the user’s profile or email — are typically OAuth scopes. These are accessible through the oauth2_permission_scopes attribute:
[
{
"admin_consent_description": "Allows the app to see your users' basic profile (e.g., name, picture, user name, email address)",
"admin_consent_display_name": "View users' basic profile",
"enabled": true,
"id": "14dad69e-099b-42c9-810b-d002981feec1",
"type": "User",
"user_consent_description": "Allows the app to see your basic profile (e.g., name, picture, user name, email address)",
"user_consent_display_name": "View your basic profile",
"value": "profile"
},
{
"admin_consent_description": "Allows the app to read your users' primary email address",
"admin_consent_display_name": "View users' email address",
"enabled": true,
"id": "64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0",
"type": "User",
"user_consent_description": "Allows the app to read your primary email address",
"user_consent_display_name": "View your email address",
"value": "email"
}
]
To access only the IDs, you can use the oauth2_permission_scope_ids output:
{
"email": "64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0",
"offline_access": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182",
"openid": "37f7f235-527c-4136-accd-4a02d197296e",
"profile": "14dad69e-099b-42c9-810b-d002981feec1"
}
These values are critical when using the azuread_application_api_access resource, which expects scope IDs rather than role IDs when dealing with delegated permissions.
Putting it all together
Here’s how you would grant Azure API Management the ability to read user profile and email data:
# Allow Azure API Management to read user profile information
resource "azuread_application_api_access" "manage_groups" {
application_id = azuread_application_registration.main.id
api_client_id = data.azuread_application_published_app_ids.well_known.result["MicrosoftGraph"]
scope_ids = [
data.azuread_service_principal.msgraph.oauth2_permission_scope_ids["email"],
data.azuread_service_principal.msgraph.oauth2_permission_scope_ids["profile"]
]
}
This configuration allows your APIM instance to call Microsoft Graph with delegated permissions to retrieve users’ basic profile and email addresses.
Conclusion
The AzureAD Terraform provider exposes a rich set of data sources for interacting with Microsoft Graph, but it’s essential to understand how and when to use roles versus OAuth scopes.
Roles are typically used for application-level access, while OAuth scopes are for delegated access on behalf of users. By leveraging outputs like app_role_ids, app_roles, oauth2_permission_scope_ids, and oauth2_permission_scopes, you can confidently configure precise access for your applications, whether working through code or aligning with what the Azure Portal creates automatically.
Familiarity with these distinctions not only improves your understanding of Azure permissions but also helps you write clearer and more maintainable infrastructure-as-code.