We need to read out distribution lists from a contact folder of a dedicated exchange/outlook mailbox (O365). The process must run as a service with no user interaction.
Unfortunately the Graph API does not support distribution lists (not even the Graph beta version does). Because of this we have to use another API - I tried using EWS.
I succeeded by granting full_access_as_app permission to our service. However this allows to read and modify ANY data in ANY mailbox which is a security risk. Granting this permission only to read out some distribution lists from one mailbox is not acceptable.
So I tried to use the ROPC flow that should allow authenticating a user and then accessing the mailbox with the permissions of this user. I followed the information here: How to get OAuth2 access token for EWS managed API in service/daemon application
(Btw I found this post linked in the discussion here: https://github.com/microsoftgraph/microsoft-graph-docs/issues/5659 which has some more information about the topic.)
I exactly followed the steps mentioned above but unfortunately this is not working: I always get a “401 Unauthorized” exception when doing the EWS calls (OAuth calls succeed) and no additional information.
According to https://developer.microsoft.com/en-us/graph/blogs/upcoming-changes-to-exchange-web-services-ews-api-for-office-365/ this is no longer working. So how can I read out distribution lists from a specific mailbox without giving full access and without an interactive login?
EDIT Here as requested the full code:
string[] ewsScopes = { "https://outlook-tdf-2.office.com/EWS.AccessAsUser.All" };
IPublicClientApplication clientApplication = PublicClientApplicationBuilder.Create(appId).WithAuthority(AzureCloudInstance.AzurePublic, tenantId).Build();
NetworkCredential credentials = new NetworkCredential(appUsername, appPassword);
AuthenticationResult authResult = await clientApplication.AcquireTokenByUsernamePassword(ewsScopes, credentials.UserName, credentials.SecurePassword).ExecuteAsync().ConfigureAwait(false);
ExchangeService exchangeService = new ExchangeService
{
Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx"),
Credentials = new OAuthCredentials(authResult.AccessToken),
};
ItemView view = new ItemView(int.MaxValue)
{
PropertySet = new PropertySet(ItemSchema.Id),
};
SearchFilter.IsEqualTo filter = new SearchFilter.IsEqualTo(ItemSchema.ItemClass, "IPM.Contact");
FindItemsResults<Item> ewsResult = await exchangeService.FindItems(WellKnownFolderName.Contacts, filter, view).ConfigureAwait(false);
I have also tried with other scopes such as "https://outlook.office.com/EWS.AccessAsUser.All" or "https://outlook.office365.com/EWS.AccessAsUser.All" but without success. I feel the problem might be related to the scope? I can see that the Exchange legacy API that was listed in the Azure UI when adding permissions is now gone...?
Yesterday Nov 19th Microsoft has updated the documentation: https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth
Following the new documentation it works (again). The main difference is to use the shortened scope "EWS.AccessAsUser.All" and not any of the full scopes found in many examples and posts such as “https://outlook.office.com/EWS.AccessAsUser.All”, “https://outlook.office365.com/EWS.AccessAsUser.All” etc.
Thank you MS for wasting my time.