A while back, we covered a nifty new feature that helps organizations restrict the scope of the all-encompassing Graph API application permissions, at least when it comes to Exchange Online. The so-called Application Access policies allow us to configure restrictions in terms of which specific objects within our tenant can a given Azure AD integrated application run Graph API calls against, addressing some of the most common concerns and complaints from customers. Should you need a refresher on how the feature works or more details, make sure to check the original article.
Now, while application access policies were a significant step forward, they did not cover all scenarios. Exchange Online is still the only workload that supports them, with most other workloads presenting no such controls or implementing alternative solutions (such as the resource-specific consent for Teams). Even within Exchange Online, an important scenario remained uncovered, namely using EWS impersonation with application permissions. Let’s talk about that for a minute.
The importance of EWS
One might ask why is addressing this scenario so important? To answer this, let’s first give a short introduction. EWS or Exchange Web Services is the primary API in the Exchange world, and the operations exposed by it are used by both end-users, by admins, and by third-party products that integrate in one way or another with Exchange. For example, many migration products use the EWS API to extract data from Exchange mailboxes and ingest it to another location, such as an Exchange Online mailbox. For admins, EWS-based tools and scripts enable bulk copying/moving of items and folders within a mailbox, manage delegation, assign retention tags, and more. On the end user’s side, EWS is used for functionalities such as fetching availability info within Outlook or Mail Tips and traditionally has been the protocol in use for the (old) Mac Outlook client.
Given the importance of EWS, Microsoft has provided a variety of settings to control protocol access on a per-user or organizational level. In addition, the Exchange RBAC model features a separate role for the so-called “EWS Application Impersonation” – the ability to run an EWS call in the context of a given user. In a nutshell, impersonation means that a given application, say EWStool, which runs in the context of the EWStool@domain.com system account, could “impersonate” the user vasil@domain.com, or CEO@domain.com for that matter. Thanks to the robust controls available in the Exchange RBAC model, it was possible to restrict this very powerful functionality in terms of who can perform the call (role assignment) and which objects within the directory they can access (management scope).
Things get a bit more complicated in the cloud
In the Office 365 world, EWS remains very useful, even after Microsoft announced a “feature freeze” and plans to replace it with the Graph API. To ensure backward compatibility and support for modern authentication protocols (OAuth), a “workaround” of sorts was introduced, blurring the lines between EWS and Graph API. Without boring you with all the details, the important thing to know is that a new mode of running EWS calls in the context of a service principal was made available, or in other words the so-called “application permissions” model. This is in contrast with the more traditional “delegated permissions” model, where code runs in the context of a given user, and the familiar Exchange RBAC controls are enforced.
Exchange and its RBAC model were not designed with “application permissions” in mind, creating some challenges. The “workaround” mentioned above made it possible to create an Azure AD integrated application and assign the full_access_as_app permission, which according to its description, “allows the app to have full access via EWS to all mailboxes without a signed-in user.” In effect, one could now create an application that gets unrestricted access to run EWS calls against any and all objects within a given organization, bypassing the RBAC model. And what’s worse, the Application access policies only work for the “delegated permissions” model, so they are also not a solution to this issue.
Well, no more. Microsoft has now extended the feature to support EWS impersonation in the application permission model as well, which means we can finally restrict access as needed!
Creating an EWS application access policy
The process of creating and configuring an Application Access policy that applies to EWS API calls doesn’t differ from the “standard” process we outlined in our previous article. As the permissions on the application object itself determine which APIs and endpoints can a given application call, all we need to do to restrict such application is to get its Identifier (AppId). Then, we simply run the New-ApplicationAccessPolicy cmdlet and provide the AppId and the desired restrictions. For example, using the below will restrict the application to only accessing a specific mailbox, specified by the value of the –PolicyScopeGroupId parameter:
New-ApplicationAccessPolicy -Description “Block EWS app permissions” -AppId aaf65e8f-XXXX-XXXX-XXXX-62efcacfceb1 -AccessRight RestrictAccess -PolicyScopeGroupId EWStestMailbox
Mike Weaver, Technical Product Manager at Quest, shows you in the video below how to use new features in Exchange Web Services to create an Application Access Policy:
You can also refer to the official documentation or our previous article for additional details.
Testing EWS application restrictions
Now that we have created an Application Access policy let’s see how it will affect EWS API calls within our organization. As per the above cmdlet, we have declared that our app will only access a single mailbox within the tenant, the one designated as “EWStestMailbox.” Even if the app has the tenant-wide, all-encompassing and unrestricted full_access_as_app permission granted! So, the first thing we want to check after creating such a policy is that only a single mailbox can be accessed.
Now, the wall of code below probably needs some explanation. We first “load” two DLLs, one for the ADAL methods and another for the EWS methods. We then obtain an access token for our application and pass it as part of the ExchangeService object’s credentials. That should be enough to ensure connectivity, which allows us to run an EWS method, such as GetDelegates() against a given mailbox. But, since we are now running in the “application permissions” model with no signed-in user, we will also need to “impersonate” a user first.
#load binaries
Add-Type -Path 'C:\Program Files\WindowsPowerShell\Modules\AzureAD\2.0.2.76\Microsoft.IdentityModel.Clients.ActiveDirectory.dll' #-PassThru
Add-Type -Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll'
#obtain token
$authContext4 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList "https://login.microsoftonline.com/tenantname.onmicrosoft.com"
$ccred = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential -ArgumentList "aaf65e8f-xxxx-xxxx-xxxx-62efcacfceb1", "verylongpasswordstring"
$authenticationResult = $authContext4.AcquireTokenAsync("https://outlook.office365.com", $ccred)
$authHeader = @{'Authorization'=$authenticationResult.Result.CreateAuthorizationHeader()}
#pass credentials
$global:exchangeService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013)
$exchangeService.Url = "https://outlook.office365.com/EWS/Exchange.asmx"
$exchangeService.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials -ArgumentList $authenticationResult.Result.AccessToken
$exchangeService.GetDelegates("EWStestMailbox@domain.com",$true) #will fails as we don’t impersonate yet
#impersonate the user allowed in our policy
$exchangeService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, "EWStestMailbox@domain.com")
$exchangeService.GetDelegates("EWStestMailbox@domain.com",$true) #no error should be returned here!
#impersonate a user NOT allowed in our policy
$exchangeService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, "shared@domain.com")
$exchangeService.GetDelegates("shared@domain.com",$true) #should throw error (403) Forbidden.
The above might look like a handful, but it’s the end result that matters here. Without the application access policy, this app would have been able to access data and perform actions against all mailboxes in our tenant, including “EWStestMailbox” and “shared.” However, with the policy in place, we are effectively limited in running code only against the mailboxes allowed per the policy, namely “EWStestMailbox” in our case. Every attempt to run against mailboxes outside of the policy scope will result in a (403) Forbidden error!
Some additional details
Now that we have created and tested our application access policy let’s turn it into some additional details. First of all, it’s important to understand that the policy does not apply to EWS only. It will act on any Graph API call as well, or in other words, application access policies are protocol agnostic. One small discrepancy between the different protocol’s implementation is the error message you will get. As detailed in our previous article, you would get a clear “Access to OData is disabled” message in the case of Graph API calls being blocked. When the policy acts on EWS calls, on the other hand, you get only the generic “403 Forbidden” reply, with no additional details provided.
Another thing to have in mind when working with EWS-based applications is that apart from impersonation, Full Access permissions are also considered. In a nutshell, you can perform certain operations on a given mailbox without having to (or being allowed to) impersonate the user, as long as you have Full access permissions granted. As an example, our app might be allowed to impersonate EWStestMailbox and blocked from impersonating Shared; however, if Full Access permissions have been granted, the EWS API call will succeed even without having to impersonate. With an application access policy in place, we are effectively restricting which objects can be impersonated. Still, if Full Access permissions have been granted, they will be honored, and the call will succeed. If you are using or evaluating the use of Application access policies, chances are you are also tightly controlling the use of Full Access permissions in your organization, but you should keep the above in mind.
Summary
In this article, we did a quick refresher on “traditional” and “modern” methods to programmatically access Exchange Online data, as well as the methods used to restrict and secure such access. The new bit here is Microsoft’s work to extend Application Access policies to cater to EWS-related scenarios. This is important, as said protocol remains widely used even years after it was announced that no future investments would be made. At the same time, securing application access is a critical topic, and Microsoft should be commended for addressing this.
Going forward, the Graph API will slowly but surely take over from EWS, and the process will probably be sped up thanks to features such as Application Access policies. While the Graph API is by design more granular when it comes to individual operations and permission scopes, being able to restrict access to specific objects on the “resource” side is an equally important part of the puzzle. As with many other security-related features across Microsoft 365, Exchange leads the way on this, and we can only hope that other teams will follow suit.
Well, please disregard my earlier post. I just realized I had a typo which is why my tests never resulted in a Denied permission. Oops.
Thank you for this explanation. I’ve been doing a lot of reading up on this as we are in need of updating Cherwell from EWS to using Modern Authentication, and this is the only method they are supporting. My question, however, is with regards to the -AccessRight switch “RestrictAccess”. We do want to limit the application to only one mailbox. Per your previous article on Application Policies on Exchange Online, you state:
“If a RestrictAccess policy exists for given Application and Target Mailbox pair, the app’s access request is granted.”, and
“If a RestrictAccess policies exists for given Application, but does not match a Target Mailbox, the app’s access request is denied.”
However, in testing my RestrictAccess policy using the command, Test-ApplicationAccessPolicy, I am getting results for all mailboxes as “granted”? I haven’t tried the code above, but shouldn’t I be getting a “denied” result with a restrict policy in place? Now I’m confused if this solution works as described?
Hi
In the documentation of Microsoft https://docs.microsoft.com/en-us/graph/auth-limit-mailbox-access it’s mentioned the application access policy is only supported for below permissions:
– Mail.Read
– Mail.ReadBasic
– Mail.ReadBasic.All
– Mail.ReadWrite
– Mail.Send
– MailboxSettings.Read
– MailboxSettings.ReadWrite
– Calendars.Read
– Calendars.ReadWrite
– Contacts.Read
– Contacts.ReadWrite
There is nothing mentioned about the full_access_as_app permission. Could you please confirm the application access policy is also working for the full_access_as_app permission or is this not supported?
Thank you in advance
Kind regards
Juan
The Real Person!
Author Vasil Michev acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.
It’s supported just fine, you can find the original blog post announcing this here: https://techcommunity.microsoft.com/t5/exchange-team-blog/application-access-policy-support-in-ews/ba-p/2110361
When the documentation will be updated, I cannot tell you. They might even not add it there, as that article is specific to the Graph API, not EWS.
The Real Person!
Author Vasil.Michev acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.
Well, nice timing on this – the SPO folks just announced the following: https://developer.microsoft.com/en-us/graph/blogs/controlling-app-access-on-specific-sharepoint-site-collections/