Why to use the custom application scope
First of all, what is the use case where the custom application scope can be useful?
You have an Entra ID application that accesses users emails or calendars without signed-in user, but you need to limit the access to a subset of users. The subset of users is defined by a filter based on user properties like department
, city
, country
, jobTitle
, displayName
, givenName
, surname
, or userPrincipalName
.
How to achieve this? The Exchange Online RBAC provider and custom application scope is the answer. Both is accessible and manageable via the Microsoft Graph API.
Exchange Online RBAC provider
The Exchange Online RBAC provider allows you to create a role assignment for a service principal to access users emails or calendars. The scope for the role assignment can be
- users in the whole tenant
- users in the group
- users in the administrative unit
- specific user
- users based a filter via the custom application scope
The custom application scope gives you more flexibility to limit the access based on a filter query that defines how you segment your recipients that the app can manage.
In short, you can limit the access to the users based on their properties like job title, department, company, given name, surname, etc.
What roles for the Exchange Online RBAC provider allow an app to access users emails or calendars? Those roles for which the role permissions contain the resource action Mail.Read
, Mail.ReadBasic
, Mail.ReadWrite
, Mail.Send
, MailboxSettings.Read
, MailboxSettings.ReadWrite
, Calendars.Read
, or Calendars.ReadWrite
.
If you are familiar with the Microsoft Graph API, resource actions may sound familiar. The names are the same as the Microsoft Graph API permissions.
To get the roles that allow an app to access users emails or calendars, you can use the following query:
GET /beta/roleManagement/exchange/roleDefinitions?$filter=rolePermissions/any(r:r/allowedResourceActions/any(a:startswith(a,'Mail') or startswith(a,'Calendars')))
Or the Graph PowerShell SDK cmdlet:
Get-MgBetaRoleManagementExchangeRoleDefinition -Filter "rolePermissions/any(r:r/allowedResourceActions/any(a:startswith(a,'Mail') or startswith(a,'Calendars')))"
The table below shows the roles returned by the query:
Id | Role name | Description |
---|---|---|
1f704712-7d46-481f-b2cd-dbcc978c4f2a | Application Mail.Read | Allows the app to read mail in all mailboxes without a signed-in user. |
3eca55c8-0e73-4c12-81bf-526549f2e5a3 | Application Mail.ReadBasic | Allows the app to read email except the body, previewBody, attachments, and any extended properties in all mailboxes without a signed-in user |
82fd214e-61ca-4dc7-98f6-090700bdb205 | Application Mail.ReadWrite | Allows the app to create, read, update, and delete email in all mailboxes without a signed-in user. Does not include permission to send mail |
8679f4ff-c91d-40d0-809c-c86d114821a5 | Application Mail.Send | Allows the app to send mail as any user without a signed-in user |
c40299e0-2107-455f-85dd-6e8862c3a0cc | Application MailboxSettings.Read | Allows the app to read user's mailbox settings in all mailboxes without a signed-in user |
459cb245-07c5-44f1-8133-3da40b4b6197 | Application MailboxSettings.ReadWrite | Allows the app to create, read, update, and delete user's mailbox settings in all mailboxes without a signed-in user |
a3123d4e-4256-4ad0-bef0-205a00807fae | Application Calendars.Read | Allows the app to read events of all calendars without a signed-in user |
b92761c0-5311-4908-92ca-2c1f8c71aa1c | Application Calendars.ReadWrite | Allows the app to create, read, update, and delete events of all calendars without a signed-in user |
b49ae303-7a8f-4ba1-aa37-27b40461aabb | Application Mail Full Access | Allows the app to create, read, update, and delete email in all mailboxes as well as send mail as any user without a signed-in user |
48d6a78c-0681-4d73-acec-9f9ffad56ddb | Application Exchange Full Access | Without a signed-in user: Allows the app to create, read, update, and delete email in all mailboxes as well as send mail as any user. Allows the app to create, read, update, and delete user's mailbox settings, events and contacts in all mailboxes |
Custom application scope
Let's take a look at how to create a custom application scope. When creating a custom application scope, you need to specify a name, whether the scope is exclusive and a filter for recipients.
If the scope is exclusive, users who match the filter query of the exclusive scope cannot be managed by other applications that have a role assignment with non-exclusive scope.
Exclusive scope is not currently supported for role assignments by the Graph API.
POST /beta/roleManagement/exchange/customAppScopes
{
"type": "RecipientScope",
"displayName": "Users from Marketing Department",
"customAttributes": {
"Exclusive": false,
"RecipientFilter": "Department -eq 'Marketing'"
}
}
Or when using the Graph PowerShell SDK (Microsoft.Graph.Beta module):
$params = @{
type = "RecipientScope"
displayName = "Users from Marketing Department"
customAttributes = @{
Exclusive = $false
RecipientFilter = "Department -eq 'Marketing'"
}
}
New-MgBetaRoleManagementExchangeCustomAppScope -BodyParameter $params
Example
Let's say you want to create two Entra ID applications that will have access to users emails.
- The first application should have access to the emails of all users from the marketing department
- The second application should have access to the emails of all managers
The steps are:
- Register Entra ID applications with the application permission
User.Read.All
(needed to query users from the marketing department and all managers) and create a client secret - Create custom application scopes for users from the marketing department and managers
- Create role assignments for each Entra ID application with the custom application scope
We can create PowerShell functions to register an application and service principal, create custom application scope and role assignment.
Create application and service principal
# Create application and service principal
function Add-EntraApplicationAndServicePrincipal {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$DisplayName,
[Parameter(Mandatory)]
[string]$ResourceId,
[Parameter(Mandatory)]
[string]$AppRoleId
)
$paramsApp = @{
displayName = $DisplayName
passwordCredentials = @(
@{
displayName = "default"
}
)
}
# create application with client secret
$app = New-MgApplication -BodyParameter $paramsApp
# save client secret to the file
$app.PasswordCredentials[0].SecretText | Out-File -FilePath "$($DisplayName)_secret.txt"
# create service principal
$paramsSp = @{
appId = $app.AppId
}
$servicePrincipal = New-MgServicePrincipal -BodyParameter $paramsSp
# wait until the service principal is provisioned
Start-Sleep -Seconds 30
# add application permission
$params = @{
principalId = $servicePrincipal.Id
resourceId = $ResourceId
appRoleId = $AppRoleId
}
$appRoleAssignment = New-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $ResourceId -BodyParameter $params
return $servicePrincipal
}
Get Exchange role definition
function Get-ExchangeRoleDefinition {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$RoleDisplayName
)
return Get-MgBetaRoleManagementExchangeRoleDefinition -Filter "displayName eq '$RoleDisplayName'"
}
Create custom application scope
function Add-ExchangeCustomApplicationScope {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$ScopeDisplayName,
[Parameter(Mandatory)]
[bool]$Exclusive,
[Parameter(Mandatory)]
[string]$RecipientFilter
)
$params = @{
type = "RecipientScope"
displayName = $ScopeDisplayName
customAttributes = @{
Exclusive = $Exclusive
RecipientFilter = $RecipientFilter
}
}
return New-MgBetaRoleManagementExchangeCustomAppScope -BodyParameter $params
}
Create role assignment with custom application scope
function Add-ExchangeRoleAssignmentWithCustomAppScope {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$ServicePrincipalId,
[Parameter(Mandatory)]
[string]$RoleDefinitionId,
[Parameter(Mandatory)]
[string]$AppScopeId
)
$params = @{
principalId = "/ServicePrincipals/$ServicePrincipalId"
roleDefinitionId = $RoleDefinitionId
directoryScopeId = $null
appScopeId = $AppScopeId
}
New-MgBetaRoleManagementExchangeRoleAssignment -BodyParameter $params
}
Now, the main part:
# Connect as admin
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Exchange", "Application.ReadWrite.All"
Import-Module Microsoft.Graph.Applications
Import-Module Microsoft.Graph.Identity.Governance
$resource = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Property "id,appRoles"
$appRole = $resource.AppRoles | Where-Object { $_.Value -eq 'User.Read.All' } | Select-Object -First 1
# Get role definition for reading emails
$roleDefinition = Get-ExchangeRoleDefinition -RoleDisplayName 'Application Mail.Read'
$roleDefinitionId = $roleDefinition.Id
# First app, scope limited to users from the marketing department
$servicePrincipal = Add-EntraApplicationAndServicePrincipal -DisplayName 'MailsReaderApp1' -ResourceId $resource.Id -AppRoleId $appRole.Id
$servicePrincipalId = $servicePrincipal.Id
$appScope = Add-ExchangeCustomApplicationScope -ScopeDisplayName 'Users from Marketing Department' -Exclusive $false -RecipientFilter "Department -eq 'Marketing'"
Add-ExchangeRoleAssignmentWithCustomAppScope -ServicePrincipalId $servicePrincipalId -RoleDefinitionId "$($roleDefinition.Id)" -AppScopeId "$($appScope.Id)"
# Second app, scope limited to managers
$servicePrincipal = Add-EntraApplicationAndServicePrincipal -DisplayName 'MailsReaderApp2' -ResourceId $resource.Id -AppRoleId $appRole.Id
$servicePrincipalId = $servicePrincipal.Id
$appScope = Add-ExchangeCustomApplicationScope -ScopeDisplayName 'Managers' -Exclusive $false -RecipientFilter "Title -like '*Manager*'"
Add-ExchangeRoleAssignmentWithCustomAppScope -ServicePrincipalId $servicePrincipalId -RoleDefinitionId "$($roleDefinition.Id)" -AppScopeId "$($appScope.Id)"
Disconnect-MgGraph
Let's test the reading emails for user from the marketing department. Connect with the client secret, filter users from the department and read their mails.
$clientId = '<app1_client_id>'
$clientSecret='<app1_client_secret>'
$clientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $clientId, (ConvertTo-SecureString -String $clientSecret -AsPlainText -Force)
$tenantId = '<tenant_id>'
# connect to Microsoft Graph API
Connect-MgGraph -ClientSecretCredential $clientSecretCredential -TenantId $tenantId
$users = Get-MgUser -Filter "Department eq 'Marketing'" -Property 'id,displayName,department'
$users | ForEach-Object {
Write-Host "Messages for user $($_.DisplayName) from $($_.Department)"
$messages = Get-MgUserMessage -UserId $_.Id -Property "id,subject"
$messages | ForEach-Object {
Write-Host "- $($_.Subject)"
}
}
Disconnect-MgGraph
The result:
If you try to call Get-MgUserMessage
for a user who is not from the marketing department, you will receive an error Access is denied. Check credentials and try again..
Be aware that this is the beta
version of the Graph API, so it may sometimes fail unexpectedly.
The script for reading managers emails is very similar:
$clientId = '<app2_client_id>'
$clientSecret='<app2_client_secret>'
$clientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $clientId, (ConvertTo-SecureString -String $clientSecret -AsPlainText -Force)
$tenantId = '<tenant_id>'
# connect to Microsoft Graph API
Connect-MgGraph -ClientSecretCredential $clientSecretCredential -TenantId $tenantId
$users = Get-MgUser -Property 'id,displayName,jobTitle' | Where-Object { $_.JobTitle -like '*manager*' }
$users | ForEach-Object {
Write-Host "Messages for user $($_.DisplayName) with job title $($_.JobTitle)"
$messages = Get-MgUserMessage -UserId $_.Id -Property "id,subject"
$messages | ForEach-Object {
Write-Host "- $($_.Subject)"
}
}
Disconnect-MgGraph
The result:
The /users
endpoint doesn't support the contains
operator for filtering users by job title, so you need to get all users and filter them in the script.
Conclusion
The Exchange Online RBAC provider and custom application scope give you more flexibility to limit the access to users emails or calendars based on the user properties. The custom application scope allows you to define a filter query that defines how you segment your recipients that the app can manage.