saif-application-permissions¶
This Terraform module manages permissions and roles for an existing Azure Entra ID (Azure Active Directory) application. It is designed to work with applications named using the pattern {project-id}-{environment-short-name}.
✨ Features¶
- 📋 API Permissions Management: Configure Microsoft Graph and other API permissions
- 🎯 Scopes: Define OAuth2 delegated scopes exposed by your application
- 🔑 App Roles: Create and manage application roles for RBAC
- 👥 Role Assignments: Assign app roles to users, groups, or service principals
- ✅ Admin Consent: Automatically grants admin consent for API permissions
- 🔓 Pre-Authorization: Pre-authorize client applications to access your application's scopes
⚠️ Important Notes¶
- This module is for existing applications only - it does not create new applications
- Default configurations (user_impersonation scope, optional claims, self-authorization) are not applied by this module
- For new applications with default configurations, use the
applicationmodule instead - Optional claims management is disabled (
configure_optional_claims=false) to prevent conflicts with applications created by theapplicationmodule
📋 Prerequisites¶
- An existing Azure AD application (created separately)
- Appropriate permissions to manage the application
- Application name following the pattern:
{project-id}-{environment-short-name}
🚀 Usage¶
Basic Example¶
module "app_permissions" {
source = "../saif-application-permissions"
project_id = "myproject"
environment_short_name = "dev"
# Application permissions (app-only, no user context)
api_roles = [
{
api_name = "Microsoft Graph"
permission_name = "User.Read.All"
},
{
api_name = "Microsoft Graph"
permission_name = "Group.Read.All"
}
]
# Delegated permissions (requires user context)
api_scopes = [
{
api_name = "Microsoft Graph"
permission_name = "User.Read"
}
]
grant_admin_consent = true
}
Advanced Example - Full Configuration with Cross-Application References¶
module "app_permissions" {
source = "../saif-application-permissions"
project_id = "myproject"
environment = "production"
environment_short_name = "prod"
# Application permissions (app-only, no user context)
api_roles = [
{
api_name = "Microsoft Graph"
permission_name = "User.Read.All"
}
]
grant_admin_consent = true
# scopes (exposing your API) - UUIDs auto-generated!
scopes = [
{
value = "Data.Read" # UUID auto-generated from value
admin_consent_description = "Allow the application to read data"
admin_consent_display_name = "Read data"
user_consent_description = "Allow the application to read your data"
user_consent_display_name = "Read your data"
type = "User"
}
]
# App roles (for RBAC) - UUIDs auto-generated!
app_roles = [
{
value = "Admin" # UUID auto-generated from value
allowed_member_types = ["User", "Application"]
description = "Administrators have full access"
display_name = "Administrator"
}
]
# App role assignments - Use role values!
app_role_assignments = [
{
role_value = "Admin" # Reference role by value
principal_object_id = "c3d4e5f6-a7b8-9012-cdef-123456789012"
}
]
# Pre-authorized applications - Cross-application references made easy!
pre_authorized_applications = [
{
project_id = "clientapp" # Automatically looks up 'clientapp-prod'
scope_values = ["Data.Read"]
},
{
project_id = "anotherapp"
scope_values = ["Data.Read", "Data.Write"]
}
]
}
Example - Exposing an API with Scopes¶
module "api_scopes" {
source = "../saif-application-permissions"
project_id = "myapi"
environment_short_name = "dev"
# Define scopes that client applications can request (UUIDs auto-generated!)
scopes = [
{
value = "Orders.Read" # UUID auto-generated from value
admin_consent_description = "Allow the application to read orders"
admin_consent_display_name = "Read orders"
user_consent_description = "Allow the application to read your orders"
user_consent_display_name = "Read your orders"
type = "User"
},
{
value = "Orders.Write" # UUID auto-generated from value
admin_consent_description = "Allow the application to write orders"
admin_consent_display_name = "Write orders"
type = "Admin" # Requires admin consent
}
]
}
Example - Application Roles for RBAC¶
module "app_rbac" {
source = "../saif-application-permissions"
project_id = "myapp"
environment_short_name = "prod"
# Define roles for your application (UUIDs auto-generated!)
app_roles = [
{
value = "Reader" # UUID auto-generated from value
allowed_member_types = ["User"]
description = "Can read data"
display_name = "Data Reader"
},
{
value = "Writer" # UUID auto-generated from value
allowed_member_types = ["User"]
description = "Can read and write data"
display_name = "Data Writer"
},
{
value = "ServiceAccount" # UUID auto-generated from value
allowed_member_types = ["Application"]
description = "Service account access"
display_name = "Service Account"
}
]
# Assign roles to users/service principals
app_role_assignments = [
{
role_value = "Reader"
business_role = "DataReader"
},
{
role_value = "Writer"
business_role = "DataWriter"
}
]
}
📊 Inputs¶
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| project_id | The project identifier (e.g., 'myproject'). Used to construct '{project_id}-{environment_short_name}' | string |
n/a | ✅ |
| environment | The full environment name (e.g., 'development', 'production'). Used for tagging and documentation. | string |
n/a | ✅ |
| environment_short_name | The short environment name (e.g., 'dev', 'prod') to construct application name | string |
n/a | ✅ |
| application | Optional application identity object { id, client_id } (e.g. { id = module.saif-appservices.application_id, client_id = module.saif-appservices.application_client_id }). Skips the display-name lookup and avoids needing a coarse depends_on on the module that created the app. Validated to reject null/empty fields. |
object({ id = string, client_id = string }) |
null |
❌ |
| service_principal | Optional service principal identity object { object_id, client_id } (e.g. { object_id = module.saif-appservices.application_principal_object_id, client_id = module.saif-appservices.application_principal_client_id }). Skips the service-principal lookup. Validated to reject null/empty fields. |
object({ object_id = string, client_id = string }) |
null |
❌ |
| api_roles | List of API application permissions (Role type) - app-only, no user context required | list(object) |
[] |
❌ |
| api_scopes | List of API delegated permissions (Scope type) - requires user context | list(object) |
[] |
❌ |
| grant_admin_consent | Whether to grant admin consent for API permissions (applies to api_roles) | bool |
true |
❌ |
| scopes | List of OAuth2 scopes to expose from this application | list(object) |
[] |
❌ |
| app_roles | List of app roles to define for this application | list(object) |
[] |
❌ |
| app_role_assignments | List of app role assignments to users, groups, or service principals | list(object) |
[] |
❌ |
| pre_authorized_applications | List of applications to pre-authorize. Reference by 'project_id' | list(object) |
[] |
❌ |
📤 Outputs¶
| Name | Description |
|---|---|
| application_object_id | The object ID of the Azure AD application |
| application_id | The application ID of the Azure AD application |
| client_id | The client ID of the Azure AD application |
| service_principal_object_id | The object ID of the service principal |
| api_permissions | List of API permissions configured |
| scopes | List of OAuth2 scopes exposed |
| app_roles | List of app roles defined |
| app_role_assignments | Map of app role assignments |
| pre_authorized_applications | List of pre-authorized applications |
| granted_permissions | Map of granted API permissions (when admin consent is granted) |
| summary | Summary of application permissions configuration |
💡 Tips¶
🎯 Default Configurations¶
This module automatically configures several critical settings for all Entra ID applications:
- 🔐 user_impersonation Scope: Every application automatically exposes a
user_impersonationscope that allows client applications to access the API on behalf of a signed-in user. This scope: - Is added by default (no configuration needed)
- Uses type
"User"(users can consent) - Follows Azure naming conventions
-
Is automatically pre-authorized for the application itself
-
👥 Groups Claim Configuration: Optional claims are configured to include groups in tokens with the following properties:
- Groups are included in access tokens, ID tokens, and SAML tokens
- Groups include
sam_account_namefor on-premises Active Directory groups - Groups are emitted as roles for easier claim processing
- This allows your application to check group membership using the
groupsclaim
🔑 Token Type Claim: The idtyp claim is included in access and ID tokens to indicate whether the token is for an application (app) or a user (user), making it easy to distinguish between service-to-service and user-delegated authentication.
- ✅ Self-Authorization: The application automatically grants admin consent to itself for the
user_impersonationscope, simplifying the consent flow for common scenarios.
⚠️ Important Notes¶
- ✨ Auto-Generated UUIDs: Scope IDs and role IDs are automatically generated from their values - no manual UUID management!
- 🎯 Platform-First Design: Use
project_idandenvironment_short_nameto reference applications - no need for UUIDs! - 📝 Intuitive API Permissions:
- Use
api_rolesfor application permissions (app-to-app, no user context) - Use
api_scopesfor delegated permissions (requires user signin) - No need to specify
type- the variable name makes it clear! - 🔗 Cross-Application References: Reference other applications by
project_id- automatically looks up{project_id}-{environment_short_name}:
pre_authorized_applications = [
{
project_id = "otherapp" # Automatically looks up 'otherapp-{env}'
scope_values = ["Data.Read"]
}
]
- 📋 Admin Consent: Defaults to
truesince application permissions require consent to be functional - 👥 Role Assignments: Assign roles using business role names - automatically maps to
bus-role.{name}(prod) orbus-role-np.{name}(non-prod) groups - 🔄 Scope Types:
- User: Users can consent themselves
- Admin: Requires admin consent
🏗️ Architecture¶
This module manages permissions for an existing application by:
- Looking up the application by display name
- Configuring API permissions the application needs (consuming APIs)
- Defining scopes the application exposes (for other apps to consume)
- Creating app roles for RBAC within your application
- Assigning roles to users/groups/service principals
- Pre-authorizing trusted client applications
📚 Related Documentation¶
🔗 Resources¶
azuread_application_registration(data source)azuread_service_principal(data source)azuread_application_api_accessazuread_app_role_assignmentazuread_application_permission_scopeazuread_application_app_roleazuread_application_pre_authorized
Note: This module is part of the SAIF Platform Terraform modules. Follow the terraform skill in .github/skills/terraform/ for development guidelines.
Features¶
- 📋 API Permissions Management: Configure Microsoft Graph and other API permissions
- 🎯 scopes: Define OAuth2 delegated scopes exposed by your application
- 🔑 App Roles: Create and manage application roles for RBAC
- 👥 Role Assignments: Assign app roles to users, groups, or service principals
- ✅ Admin Consent: Optionally grant admin consent for API permissions
- 🔓 Pre-Authorization: Pre-authorize client applications to access your application's scopes
Usage¶
Basic Example¶
module "app_permissions" {
source = "../saif-application-permissions"
application_name = "myproject-dev"
api_permissions = [
{
api_name = "Microsoft Graph"
permission_name = "User.Read.All"
type = "Role"
}
]
grant_admin_consent = true
}
Requirements¶
Requirements¶
| Name | Version |
|---|---|
| terraform | >= 1.0.0, < 2.0.0 |
| azuread | >= 3.0, < 4.0 |
Providers¶
Providers¶
| Name | Version |
|---|---|
| azuread | 3.9.0 |
Inputs¶
Inputs¶
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| app_role_assignments | List of app role assignments to business role groups. Groups are looked up by business role name and environment. | list(object({ |
[] |
no |
| app_roles | App roles that this application exposes to other applications or users | list(object({ |
[] |
no |
| application | Optional application identity to pass in directly instead of looking up the application by display name. When set, both id (the application object ID) and client_id must be provided together, e.g. { id = module.saif-appservices.application_id, client_id = module.saif-appservices.application_client_id }. Passing a single object (instead of separate id/client_id/boolean inputs) lets count safely branch on var.application == null even when the id/client_id values themselves are unknown at plan time (e.g. because the source module's application resource is being created or replaced) — an object literal with unknown attribute values is still a known, non-null object, so there's no risk of an 'Invalid count argument' error. Leave null to look the application up by '{project_id}-{environment_short_name}'. |
object({ |
null |
no |
| environment | The full environment name (e.g., 'development', 'production'). Used for tagging and documentation purposes. | string |
n/a | yes |
| environment_short_name | The short environment name used to construct the application name as '{project_id}-{environment_short_name}' (e.g., 'dev', 'prod'). | string |
n/a | yes |
| pre_authorized_applications | List of applications to pre-authorize for accessing this application's scopes and/or app roles. Reference applications by their project_id. | list(object({ |
[] |
no |
| project_id | The project identifier for the application (e.g., 'myproject'). Used to construct the application name as '{project_id}-{environment_short_name}'. | string |
n/a | yes |
| requested_application_permissions | Application permissions (Role type) to request from other APIs like Microsoft Graph. These are app-only permissions that don't require user context. | list(object({ |
[] |
no |
| requested_delegated_permissions | Delegated permissions (Scope type) to request from other APIs like Microsoft Graph. These permissions require user context. | list(object({ |
[] |
no |
| scopes | OAuth2 scopes that this application exposes to other applications | list(object({ |
[] |
no |
| service_principal | Optional service principal identity to pass in directly instead of looking it up via the application's client ID. When set, both object_id and client_id must be provided together, e.g. { object_id = module.saif-appservices.application_principal_object_id, client_id = module.saif-appservices.application_principal_client_id }. See application for why a single object is used instead of separate id/client_id/boolean inputs. Leave null to look the service principal up automatically. |
object({ |
null |
no |
Outputs¶
Outputs¶
| Name | Description |
|---|---|
| api_permissions | List of API permissions configured for the application |
| app_role_assignments | Map of app role assignments |
| app_roles | List of app roles defined for the application |
| application_id | The application ID (client ID) of the Azure AD application |
| application_object_id | The object ID of the Azure AD application |
| client_id | The client ID of the Azure AD application |
| granted_permissions | Map of granted API permissions with their details (admin consent granted) |
| pre_authorized_applications | List of pre-authorized applications |
| scopes | List of OAuth2 scopes the application |
| service_principal_object_id | The object ID of the service principal |
| summary | Summary of application permissions configuration |
Resources¶
Resources¶
| Name | Type |
|---|---|
| azuread_application.application | data source |
| azuread_group.role_assignments | data source |
| azuread_service_principal.application_sp | data source |