Azure Active Directory v2 and Exchange Web Services

It seems that much of my life in development involves some aspect of integrating the older side of technology with the new. Today was no exception.

Whilst working on a new Office 365 version of Affixa Enterprise today, I found myself trying to get the Exchange Web Services (EWS) SOAP web service to play nicely with Azure Active Directory's (AAD's) latest OAuth2 implementation.

I had three goals:

  1. Use an OAuth2 Bearer token issued by AAD v2 for authorisation;
  2. Impersonate a particular AAD user; and
  3. Get their email signature.

It's the first of these that took up a whole day's worth of trial, error and tenacity today.

Why use EWS, anyway?

Well, fair question. There are REST APIs for Office 365, of course, but they're still not as feature-rich as the good, old-fashioned SOAP APIs.

Try getting a user's email signature via REST, for instance; you simply can't do it!

Why is authorisation so hard?

The good news is that the Office 365 version of EWS does allow access using an OAuth2 Bearer token! So just get an access token, stick it in an Authorization header and we're done, right? No, sadly.

The bad news is that pretty much all the documentation you'll find online refers to AAD v1. There are some really great v1 step-by-step guides available, but as soon as you try and approximate those steps in AAD v2, you soon run into obstacles.

For instance, here's the scope you're meant to select in the old Windows Azure portal to allow a AAD v1 application access to EWS according to all the guides:

I'd love to show you the equivalent UI in the Application Registration Portal that's used to create v2 applications. Unfortunately, I can't, simply because there isn't an equivalent!

Step-by-Step Guide

1. Getting at the elusive EWS scope

Edit the Application Manifest manually to insert the EWS scope.

The only way I figured this out was to go back to the original Windows Azure portal, create a temporary v1 application and follow the v1 guides available on the interwebs.

Once I'd created an application that included the "Use Exchange Web Services" scope, I exported the Application Manifest. This is downloaded to your computer as a JSON file.

Opening it up in a text edtor, I could see the part that referred to the EWS application and the role I'd selected via the admin UI.

{
      "resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
      "resourceAccess": [
        {
          "id": "dc890d15-9560-4a4c-9b7f-a736ec74ec40",
          "type": "Role"
        }
      ]
    }

Note: in Microsoft's OAuth2 terminology, an application permission is called a 'role'; a user permission is a 'scope'.

The trick was to merge this JSON object into the v2 Application Manifest file.

To do this, log into the Application Registration Portal, choose your application and then jump all the way down to the bottom. Click the "Edit Application Manifest" button.

This will bring up an editor screen that will allow you to merge the above JSON. Save the changes, and don't forget to grant admin consent for your newly-added scope.

2. Know your audience

It's important that your JWT access token has an aud (audience) value of https://outlook.office.com, otherwise EWS will reject the access token.

Pretty much every authentication guide out there for AAD v2 uses a scope of https://graph.microsoft.com/.default. When you use this scope, your access token is given all the permissions you'd set up in the Application Registration Portal automatically, so it's really convenient to use.

However, the scopes requested also drive the aud value in the access token issued by the OAuth2 token endpoint, so following the majority of the guides out there will give you an aud value of https://graph.microsoft.com. This is a bad thing if we're using EWS.

So two golden rules apply:

  1. only ever include Outlook-related scopes in the list of requested scopes; and
  2. always use the fully-qualified scope URIs.

In simple terms, this means using https://outlook.office.com/mail.read instead of Mail.Read.

Doing this will result in an access token with an aud of https://outlook.office.com. You can confirm this by copying the text of your access token and pasting it at the jwt.io parser.

3. Something on the side

You can't just ask for the scope relating to EWS access when requesting an access token. You need to ask for something else, and get the EWS scope as a bonus.

You'll need to ask for another mail-related scope in order to get an access token for EWS. It doesn't seem to matter which scope you ask for, but you can't just ask for the EWS scope alone.

So for example, when requesting scopes during the authorisation dance, you'd ask for https://outlook.office.com/mail.read and receive a JWT access token. If you decode that access token over at jwt.io, you'll notice the scp (scope) value includes Mail.Read but also all_mailboxes_as_app.

The latter is the scope that allows us to access EWS.

Hurrah. We finally have an access token that EWS will like!

4. Headers, headers and more headers

Thankfully, impersonating a user in EWS still works as per the EWS documentation.

To access EWS with our access token, we'll need to do three things:

  1. set the Authorization HTTP header, e.g. Authorization: Bearer eyfhshijsfhfs0hfshs==;
  2. set an X-AnchorMailbox HTTP header with the value being the email address of the user you want to impersonate, e.g. X-AnchorMailbox: [email protected]; and
  3. set a SOAP header indicating the email address of the user to impersonate.

Number 3 above can be done as follows if you're using EWS Managed API for .NET:

exchangeServiceInstance.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SMTP, "[email protected]");  

Alternatively, if you're just using the System.Net.HttpClient like me, your SOAP header needs to look like this:

  <soap:Header>
    <t:RequestServerVersion Version=""Exchange2016"" />
      <t:ExchangeImpersonation>
    <t:ConnectingSID>
      <t:PrimarySmtpAddress>[email protected]</t:PrimarySmtpAddress>
    </t:ConnectingSID>
  </t:ExchangeImpersonation>
  </soap:Header>

Gotchas and Red Herrings

One thing I've run into is that even though all the Graph scopes used by my applications can be approved by an admin, individual users still have to approve access the first time they log in. I'm putting this down to an oddity of using a non-Graph API and in our case this isn't the end of the world.

One other thing I read on StackOverflow was this:

One last final caveat: the OAuth permission scope required for EWS are not portable like the other permission scopes are. What I mean by that is that unlike a REST API app, where you can register it in Azure using your developer tenant, and then other Office 365 organizations can just consent to your app, EWS apps that use OAuth have to be registered separately in each tenant that uses them. If you're creating this app for your own organization, not so big a deal. But if you were planning on licensing this app to other organizations, something you should be aware of.

This alarmed me when I first read it because this would've been a huge issue for us, but it seems that it's no longer the case. I was able to approve our app for another Office 365 tenant and access EWS impersonating their users without any difficulty.

Chris Wood

Entrepreneur, developer, consultant, rugby league fan and dad.

Wigan, England