Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get OAuth2 access token for EWS managed API in service/daemon application

Scenario

I have an Exchange Online environment and service/daemin (no interactive user) application on the Azure VM. Service uses EWS managed API to work with emails in the mailbox of any tenant user. Now EWS client uses Basic authentication that, according to Microsoft, will become unsupported in EWS to access Exchange Online.

Question/Issue

So, I need to find a way to get valid access token for service/daemon application to use with EWS managed API.

My findings

The following article shows an example of using OAuth 2.0 with EWS managed API. This example works, but it uses interactive method of getting consent (sign-in form appears allowing user authenticate themselves and grant requested permission to application) that is not suitable for service/daemon app scenario, because there is no interactive user.

For service/daemon application I need to use client credential authentication flow.

Registered application

Using admin account on https://aad.portal.azure.com portal I registered application with Azure Active Directory. Added client secret for registered application.

Aforementioned article uses https://outlook.office.com/EWS.AccessAsUser.All as a scope. But I did not find permission with such a URL on the portal. I found only the following permissions under Office 365 Exchange Online > Application permissions > Mail:

  1. https://outlook.office365.com/Mail.Read Allows the app to read mail in all mailboxes without a signed-in user
  2. https://outlook.office365.com/Mail.ReadWrite Allows the app to create, read, update, and delete mail in all mailboxes without a signed-in user.

I added both of them and granted admin consent for all users.

Getting access token

For testing purposes and simplicity I did not use any auth libraries (ADAL, MSAL etc.). I used Postman to get access token, then set token variable in debug (see code snippet later in the post).

I tried different endpoints to get acess token.

  1. OAuth 2.0 token endpoint (v2)
    POST: https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token
        grant_type=client_credentials
        client_id=*** 
        client_secret=***
        scope=https://outlook.office.com/EWS.AccessAsUser.All

Sending this request produces the following error response:

AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope https://outlook.office.com/EWS.AccessAsUser.All is not valid.

I tried changing scope to https://outlook.office.com/.default. Access token was returned, but it appeared to be invalid for EWS. EWS client throws 401 error with the following value of x-ms-diagnostics response header:

2000008;reason="The token contains no permissions, or permissions can not be understood.";error_category="invalid_grant"

  1. OAuth 2.0 token endpoint (v1)
    POST: https://login.microsoftonline.com/<TENANT_ID>/oauth2/token
        grant_type=client_credentials
        client_id=*** 
        client_secret=***
        resource=https://outlook.office.com

Access token was returned, but also appeared to be invalid for EWS. EWS client throws 401 error with the same value of x-ms-diagnostics response header as described ealier in #1.

Use aquired access token with EWS managed API

Here is code sample that I used to test EWS client with access token acquired in Postman:

var token = "...";
var client = new ExchangeService
{
    Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx"),
    Credentials = new OAuthCredentials(token),
    ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress,
                 "[email protected]"),

};
var folder = Folder.Bind(client, WellKnownFolderName.SentItems);
like image 784
Oleksii Avatar asked Jul 12 '19 15:07

Oleksii


People also ask

How do I get an access token from an authorization server?

After you add the authorization profile, you need to get access token from the server. In this tutorial, we get it by using the Authorization Code grant method: Click Get Token. In the subsequent dialog, enter Client Identification and Secret, Authorization URI, Access Token URI and Redirect URI.

Does EWS use OAuth?

Use OAuth authentication in all your new or existing EWS applications to connect to Exchange Online. OAuth authentication for EWS is only available in Exchange Online as part of Microsoft 365. EWS applications that use OAuth must be registered with Azure Active Directory first.

How do I authenticate an EWS application by OAuth?

To use OAuth with your application you will need to: Register your application with Azure Active Directory. Add code to get an authentication token to get an authentication token from a token server. Add an authentication token to EWS requests that you send.


2 Answers

You can protect your client application with either a certificate or a secret. The two permissions that I needed to get this to work were Calendars.ReadWrite.All and full_access_as_app. I never tried acquiring my token via PostMan, but use AcquireTokenAsync in Microsoft.IdentityModel.Clients.ActiveDirectory. In that call, the resource parameter I use is https://outlook.office365.com/. It's pretty simple once you know all the little twists and turns. And full disclosure: I was one lost puppy until MSFT support helped me through this. The doc on the web is often outdated, conflicting, or at best, confusing.

like image 150
pjneary Avatar answered Sep 16 '22 12:09

pjneary


We had a similar problem: We wanted to use a Service Account to connect to a single mailbox and just doing some stuff with the EWS API (e.g. searching in the GAL) and the full_access_as_app seems like an overkill. Fortunately it is possible:

  1. Follow the normal "delegate" steps

  2. And use this to get a token via username/password:

...
var cred = new NetworkCredential("UserName", "Password");
var authResult = await pca.AcquireTokenByUsernamePassword(new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" }, cred.UserName, cred.SecurePassword).ExecuteAsync();
...
  1. To make this work you need to enable the "Treat application as public client" under "Authentication" > "Advanced settings" because this uses the "Resource owner password credential flow". (This SO answer helped me alot!)

With that setup we could use a "tradional" username/password way, but using OAuth and the EWS API.

like image 24
Robert Muehsig Avatar answered Sep 16 '22 12:09

Robert Muehsig