If you are writing solutions that target M365 services, you need to plan for permissions that your application will have to the M365 resources.
It doesn't really matter if you are using Azure Services with Managed Identity, or a custom application with Service Principal; you have to set API permissions.
Let's imagine an application that needs to synchronize information between "Events" SharePoint Online list and an Exchange Online mailbox belonging to a group responsible for organizing these events. Invitations are sent to members of the group and replies need to be recorder in the list.
This means that the application needs to
Write to both: the SharePoint list and the Exchange calendar:
The business logic is implemented using Azure Function, with System-Managed Identity.
Some tests are performed using Postman, with a Service Principal.
I'm a staunch believer in a minimum-required permissions and would never implement an application that has indiscriminate access to all resources using Graph API (unless specifically required, that is).
Sites.Selected Application API permissions that together with
Grant-PnPAzureADAppSitePermission allow granting
Set-PnPAzureADAppSitePermission) permissions to the application identity.
But what about the mailboxes? The API permissions for Calendars grant access to all the mailboxes. That's a bit of overreach, innit?
Luckily there's a way to set granular access rights on the Exchange Online mailboxes as well!
There are two ways to do it:
- Access Policy for Applications in Exchange Online: Limiting application permissions to specific Exchange Online mailboxes - Microsoft Graph
- RBAC: Role Based Access Control for Applications in Exchange Online (Preview)
However, "App Access Policies will soon be replaced by Role Based Access Control for Applications." New-ApplicationAccessPolicy (ExchangePowerShell)
The procedure requires Exchange Online PowerShell
Let's use the Service Principal created for Postman.
In order to retrieve the information required for setting RBAC, you need to find out the Object and Application IDs belonging to the Enterprise Application and not the App Registration. Click on the App name under "Managed application in local directory"
You will be redirected to "Enterprise Application" page displaying the IDs you need to use:
Next, we need to create a Service Principal. Why?
In Exchange Online, service principals are references to the service principals in Azure AD. To assign Exchange Online role-based access control (RBAC) roles to service principals in Azure AD, you use the service principal references in Exchange Online.
Import-Module ExchangeOnlineManagement Connect-ExchangeOnline -UserPrincipalName New-ServicePrincipal ` -AppId f3776ca2-2d7c-48f8-9e3c-7xxxxxxxxxxx ` -ObjectId b7cb2398-1e91-4a8b-95b8-xxxxxxxxxxxxx ` -DisplayName "Postman"
The Role Based Access Control for Applications in Exchange Online (Preview) proposes two resource scopes for defining who has the RBAC:
- Management Scopes and
- Administrative Units
You have to go with Management Scopes to use the Managed Identity/Service Principals. Administrative units don't allow these.
New-ManagementScope ` -Name "My Scope" ` -RecipientRestrictionFilter "Alias -eq 'mailboxname' "
And now we can grant the Service Principal the RBAC to the mailbox:
New-ManagementRoleAssignment ` -App "f3776ca2-2d7c-48f8-9e3c-7xxxxxxxxxxx" ` -Role "Application Calendars.ReadWrite" ` -CustomResourceScope "My Scope"
Test-ServicePrincipalAuthorization ` -Identity "f3776ca2-2d7c-48f8-9e3c-7xxxxxxxxxxx" ` -Resource firstname.lastname@example.org
- Applications can't become member of a Role Group.
- Application roles can only be assigned to Service Principals.
- Application roles can't be copied or derived.
- Exclusive management scopes don't restrict app access.
- Changes to app permissions are subject to cache maintenance that varies** between 30 minutes and 2 hours **depending on the app's recent usage. When testing configurations, the test command bypasses this cache. An app with no inbound calls to APIs will have its cache reset in 30 minutes, whereas an actively used app will keep its cache alive for up to 2 hours.
Import-Module ExchangeOnlineManagement Connect-ExchangeOnline -UserPrincipalName $scopeName="" $mailboxName="" $tenantName="" $appName="" $appId="" $objId="" New-ServicePrincipal -AppId $appId -ObjectId $objId -DisplayName $appName New-ManagementScope -Name $scopeName -RecipientRestrictionFilter "Alias -eq $mailboxName " New-ManagementRoleAssignment -App $appId -Role "Application Calendars.ReadWrite" -CustomResourceScope $scopeName Test-ServicePrincipalAuthorization -Identity $appId -Resource "$mailboxName@$tenantName.onmicrosoft.com"