ExO RBAC improvements #1: Limiting application access

Exchange has long been the prime example of a workload with granular and robust permissions model. Thus, it comes as no surprise that the Azure AD role-based access control (RBAC) model follows most of the principles introduced by Exchange. With all its customizability however, the RBAC model had some issues addressing the application permissions model introduced by the Graph API, where the service principal, under which the various operations were being executed, gains unscoped, directory-wide permissions.

To address such scenarios, Exchange Online introduced support for application access policies back in 2019. Later on, the EWS application permissions scenario was also addressed. Other workloads added their own implementations of similar controls, such as the Sites.Selected method for SharePoint Online and the resource-scoped consent model for Teams, to an extent. Yet, such implementations are not without their own issues, and in the case of Exchange certain limitations were quickly identified. So instead of making further investments in application access policies, Microsoft decided to bring the application experience as part of the existing Exchange RBAC model.

Meet Role Based Access Control for Applications in Exchange Online, currently in public preview. In a nutshell, the feature allows you to treat application access in a manner similar to how you assign user permissions. To that end, you need to decide on the set of permissions needed (the Management Role), the principal to grant them to (a Service principal object in this case) and the set of objects against said permissions will be valid (the Management Scope). The new bits here are support for service principal objects, i.e. the object representing an Azure AD integrated app within your directory, and the introduction of new, service principal specific management roles.

Incidentally, we’ve already covered all those new bits individually. At the time I wrote said article, management role assignment was not yet available for SP objects, but I speculated this will be coming in the future. And so, the time has come. For the time being, you will still need to manually create a matching service principal object for any application you plan to restrict access on, by means of using the New-ServicePrincipal cmdlet. Going forward, Microsoft is promising that this step will no longer be needed. Here’s an example on how to use the cmdlet. You will need to specify the client ID (application ID) value, via the –AppId parameter, as well as the object ID, via the –ServiceId parameter:

New-ServicePrincipal -AppId xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -ServiceId xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

If you do not know the values for said parameters, you can obtain them from either the Azure AD blade > App registrations > Overview page, or via PowerShell (Get-MsolServicePrincipalGet-AzureADServicePrincipal or Get-MgServicePrincipal cmdlet, depending on which module you prefer). Do note that the New-ServicePrincipal does NOT validate the values, so make sure you double- and triple-check them.

The other part of the puzzle is the set of Management Roles you can assign. Microsoft has created a set of such roles, corresponding to the (most used) Graph API permissions. You can obtain a list of all supported roles by filtering them via the IsServicePrincipalRole value of True. Alternatively, you can look for all roles with the “Application” prefix. Do note that the list does not include all application permissions scopes available in the Graph!

Get-ManagementRole | ? {$_.IsServicePrincipalRole} | select Name,IsServicePrincipalRole,Description | sort Name

Name IsServicePrincipalRole Description
---- ---------------------- -----------
Application Calendars.Read True Allows the app to read events of all calendars without a signed-in user
Application Calendars.ReadWrite True Allows the app to create, read, update, and delete events of all calendars without a signed-in user
Application Contacts.Read True Allows the app to read all contacts in all mailboxes without a signed-in user
Application Contacts.ReadWrite True Allows the app to create, read, update, and delete all contacts in all mailboxes without a signed-in user
Application EWS.AccessAsApp True Allows the app to use Exchange Web Services with full access to all mailboxes
Application Exchange Full Access True 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. A...
Application Mail Full Access True 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
Application Mail.Read True Allows the app to read email in all mailboxes without a signed-in user
Application Mail.ReadBasic True Allows the app to read email except the body, previewBody, attachments, and any extended properties in all mailboxes without a signed-i...
Application Mail.ReadWrite True Allows the app to create, read, update, and delete email in all mailboxes without a signed-in user. Does not include permission to send...
Application Mail.Send True Allows the app to send mail as any user without a signed-in user
Application MailboxSettings.Read True Allows the app to read user's mailbox settings in all mailboxes without a signed-in user
Application MailboxSettings.ReadWrite True Allows the app to create, read, update, and delete user's mailbox settings in all mailboxes without a signed-in user

After this introduction/refresher, how do we go about replacing an existing application access policy with a matching role assignment? The task boils down to determining the set of objects the application access policy was scoped to, creating a matching management scope and creating a management role assignment for the corresponding service principal. If you are not using application access policies but only interested in creating a management role assignment for a SP, just skip the next paragraph. The process remains the same, we simply need to provide the required information, instead of “borrowing” it from the properties of an existing application access policy.

Let’s start by looking at the application access policy details. The Identity property gives us the ClientID of the application, as well as the object to which it’s scoped. The only other bit of information we need is the type of policy, determined by the value of the AccessRight property.


ScopeName : USG
ScopeIdentity : USG
Identity : 923712ba-352a-4eda-bece-09d0684d0cfb\ae23ad05-9b02-4946-a906-78b7a0757f5f:S-1-5-21-3675944716-2045640655-299186705-9086853;9e629d33-d655-440c-89af-15738e59e667
ScopeIdentityRaw : S-1-5-21-3675944716-2045640655-299186705-9086853;9e629d33-d655-440c-89af-15738e59e667
Description : Test for app permissions
AccessRight : DenyAccess

We can use the New-ServicePrincipal cmdlet to create a matching object within ExODS (see example above).

Next, we need a management scope. Few notes are due here. With application access policies, the scope was either a single mailbox/mail user object or a set of objects, designated by their membership of a mail-enabled security group. Using “standard” Exchange management scopes, we have a lot more flexibility as you can use a variety of properties and operators to build the recipient filter. If you simply want to match the scope of the application access policy, use the MemberOfGroup property as follows:

$dn = (Get-Recipient 9e629d33-d655-440c-89af-15738e59e667).DistinguishedName

Get-Recipient -RecipientPreviewFilter "MemberOfGroup -eq '$dn'"

Name RecipientType
---- -------------
vasil UserMailbox
HuKu UserMailbox

where we’ve used the GUID obtained from the application access policy configuration. The Get-Recipient cmdlet can be used to “resolve” said GUID to the matching object within the directory, and after that we can leverage its DistinguishedName value to prepare the Recipient filter.

You might have noticed however that the application access policy was configured in “deny” mode, i.e. it was preventing the application from performing any operations against members of the specified group, while all other objects within the directory were valid targets. Thus, we need to amend our management scope query to exclude members of this group, i.e. use the “opposite” filter query:

Get-Recipient -RecipientPreviewFilter "MemberOfGroup -ne '$dn'"

Once you are satisfied with the set of object returned by the query, proceed with creating the matching management scope:

New-ManagementScope -Name "Exclude members of USG" -RecipientRestrictionFilter "MemberOfGroup -ne '$dn'"

Name ScopeRestrictionType Exclusive RecipientRoot RecipientFilter
---- -------------------- --------- ------------- ---------------
Exclude members of USG RecipientScope False MemberOfGroup -ne 'CN=USG,OU=michev.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=EURPR03A001,DC=prod,DC=outlook...

It’s worth mentioning that only direct members of the group will fall under the scope of the filter (or out of it, when we use the -ne operator). In other words, nested groups are not supported. You can however use regular distribution groups and Microsoft 365 Groups in addition to mail-enabled security groups. In addition, you can create a scope based on membership of an administrative unit, but we will cover this in another article.

With that, we have all the building blocks to create a management role assignment that will restrict access for the given application. We have an object representing the application and the set of objects against which access will be granted. The last thing to decide on is what permissions the app will get, in other words which Management role(s) to assign to it. While in the case of application access policies this was configured entirely on Azure AD side, with the extensions to the Exchange RBAC model you can control the set of permissions independently, by assigning one (or more) of the service principal roles we listed above.

The management role assignment is done by executing the New-ManagementRoleAssignment cmdlet. Use the newly introduced –App parameter to specify the service principal object and the –Role parameter to assign the corresponding management role. The –CustomResourceScope parameter is optional one and serves to designate the management scope, if you plan to use one. Without it, the newly created management role assignment will allow the application to act upon any object within the tenant. Combining those, we can now create the new role assignment:

New-ManagementRoleAssignment -App 2a63aee1-db17-489d-a8ab-d40971066292 -Role "Application Mail.ReadBasic" -CustomResourceScope "Exclude members of USG"

In effect, we have mirrored the behavior of the existing application access policy, which restricted the given app to act on every object within the directory, excluding members of the specified group. In addition we’ve ensured that only specific Graph API operations can be executed, independent of any permissions the app has been granted on Azure AD side. If we want to provide the app with unrestricted access, we can create a management role assignment for the Application Exchange Full Access role instead and remove the management scope, if needed.

It’s worth clarifying that the Exchange RBAC permissions do not override application access policies, thus you need to carefully consider the settings you configure. What Microsoft suggests (and I concur with) is to remove the application access policy once a matching role assignment has been created. If both an application access policy and management role assignment are in effect for the same app, the resulting experience can be a bit confusing. In a nutshell, both sets of restrictions are applied in a logical OR configuration, as detailed in the official documentation.

Once the new role assignment is created, it’s worth running some additional checks to ensure everything is configured as it should be. The best approach would be to test the application itself, but Microsoft has also provided a new cmdlet that should help diagnose some issues, namely Test-ServicePrincipalAuthorization. You run the cmdlet by providing the id of the service principal you want to test, and optionally a resource to test against, such as a mailbox name. Here are some examples:

#Resource not in scope
Test-ServicePrincipalAuthorization -Identity 2a63aee1-db17-489d-a8ab-d40971066292 -Resource vasil

RoleName GrantedPermissions AllowedResourceScope ScopeType InScope
-------- ------------------ -------------------- --------- -------
Application Mail.ReadBasic Mail.ReadBasic Exclude members of USG CustomRecipientScope False

#Resource in scope
Test-ServicePrincipalAuthorization -Identity 2a63aee1-db17-489d-a8ab-d40971066292 -Resource shared

RoleName GrantedPermissions AllowedResourceScope ScopeType InScope
-------- ------------------ -------------------- --------- -------
Application Mail.ReadBasic Mail.ReadBasic Exclude members of USG CustomRecipientScope True

In summary, the Exchange Online’s RBAC model has been extended to cover some application-specific scenarios, by means of adding support for delegating management roles to service principal objects. Not only this approach unifies the application scenario with the existing RBAC controls, it also addresses the shortcomings of the application access policies feature, which was our only option to restrict application access until now, and adds some additional possibilities on top of it. While the feature is still in preview and there are some rough edges, Microsoft is already planning to address them, and most importantly provide an UI. And, unlike some of the recent announcements on Microsoft side, we’re getting all this for free, without requiring any add-on licenses or new SKUs. Amen.

One last thing before closing – you cannot use exclusive scopes for app permissions. Just in case I’ve neglected to answer some question, here’s a link to the official announcement, as well as the preview documentation. Next, we will look into assigning “regular” management roles to service principal objects, which allows us to limit CBA scenarios, as well as the added support for management scopes based on administrative units. Stay tuned!

5 thoughts on “ExO RBAC improvements #1: Limiting application access

  1. Jan says:

    Many thanks for this>

    My module does not know -RecipientAdministrativeUnitScope parameter.
    Any tips? Do I need a preview module perhaps?

    1. Vasil Michev says:

      As answered over email, -RecipientAdministrativeUnitScope is parameter for the New-ManagementRoleAssignment cmdlet, not New-ManagementScope.


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.