In this post I'm going to look at what you need to do in your EWS Managed API code to support using Hybrid Modern Authentication where previously you've been using Basic or Integrated Authentication (both of which are susceptible to password spray attacks). If you don't know what Hybrid Modern Authentication is put simply it brings to Exchange OnPrem email clients the security benefits of Modern Authentication offered by Azure AD to Office365 tenants. If your already using OAuth to connect to Office365 you have most of the work already done but you will still need logic to ensure you have the correct Audience set in your token when that code is used against an OnPrem Mailbox.
Prerequisites
You need to be using Hybrid Exchange or more specifically
Hybrid Office 365 tenant is configured in full hybrid configuration using Exchange Classic Hybrid Topology mode ref https://docs.microsoft.com/en-us/exchange/clients/outlook-for-ios-and-android/use-hybrid-modern-auth?view=exchserver-2019
And you need to configure Hybrid Modern Authentication https://docs.microsoft.com/en-us/office365/enterprise/configure-exchange-server-for-hybrid-modern-authentication
If you don't want to enable Hybrid Modern Authentication but still want to use oAuth in EWS you can do it and there is a good article by Ingo on how to do this https://practical365.com/exchange-server/configure-hybrid-modern-authentication-for-exchange-server/
Authentication - Acquiring the Token
This is where you need to make the most changes in your current code as you will now need some logic that can be used to acquire the oAuth Tokens from AzureAD. The easiest way of doing this is to use one of the Authentication libraries from Microsoft either ADAL (if you already have this implemented in your code) or preferably use the MSAL library. The difference between ADAL and MSAL is ADAL uses the v1 Azure oauth endpoint and MSAL uses the v2 there is a good description of the differences between the two endpoints https://nicolgit.github.io/AzureAD-Endopoint-V1-vs-V2-comparison/
Getting the intended Audience value for you Token Request
When your using Hybrid Modern Authentication the Audience value for your token will become the external EWS endpoint's host-name of your OnPrem server (generally what you have configured in get-webservicesvirtualdirectory)
In the Authentication libraries this Audience is passed differently in
ADAL v1 Azure Endpoint its passed as the resourceURL
String ResourceURL = ""https://outlook.office365.com"; var AuthResults = AuthContext.AcquireTokenAsync(ResourceURL , "xxxxx-52b3-4102-aeff-aad2292ab01c", new Uri("urn:ietf:wg:oauth:2.0:oob"), new PlatformParameters(PromptBehavior.Always)).Result;
In MASL v2 Azure Endpoint its passed as part of the scope
string scope = "https://outlook.office365.com/EWS.AccessAsUser.All"; PublicClientApplicationBuilder pcaConfig = PublicClientApplicationBuilder.Create(ClientId).WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs); var IntToken = pcaConfig.Build().AcquireTokenInteractive( new[] { scope }).ExecuteAsync().Result;
With Hybrid Modern Auth in the above examples outlook.office365.com would be replaced with the host name for your external EWS endpoint which you would obtain usually via Autodiscover
AutoDiscover
Autodiscover in Exchange from Exchange 2007 has been there to help you basically discover the internal or external endpoint you need for whatever API your using. It is however an Authenticated Endpoint so when you remove Basic/Intergrated Authentication your code needs to be able to deal with this change. There are two ways you could go about this the first is generate a OAuth token first and use that to make the Authenticated traditional Auto-discover request. Or the second way is to use Autodiscover v2 (or Autodiscover json) which allows you to make an unauthenticated autodiscover requests to return the API endpoint you want.
If your code targets predominately Office365 and Hybrid tenants then just switch to use Autodiscover v2, if you have a mix of Office365, Hybrid and OnPrem islands then you still need the legacy Basic/Integrated Auth method for these OnPrem clients. The Approach that I'm taking in this post is to first do a Realm discovery against Office365 to determine if a particular set of credentials is an Office365 or Hybrid account.If it is then a v2 Autodiscover request will be made against Office365 and if not fail back to the legacy code. This isn't 100% guaranteed to work for some OnPrem (especially pre Exchange 2016) and account combinations so my advice is you always make sure your try/catch autodiscover logic includes at least one legacy auto discover-attempt as a last fail back. And make sure you do some regression testing on your code change against Exchange 2013.
For doing a simple JSON based Autodiscover against Office365 this can be done in a few lines with httpclient in c#
String MailboxName = "gscales@datarumble.com"; String EWSEndPoint = $"https://outlook.office365.com/autodiscover/autodiscover.json/v1.0/{MailboxName}?Protocol=EWS"; HttpClient httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (compatible; AcmeInc/1.0)"); dynamic JsonResult = JsonConvert.DeserializeObject(httpClient.GetAsync(EWSEndPoint).Result.Content.ReadAsStringAsync() .Result); Console.WriteLine(JsonResult.Url);
Or in PowerShell you could do it as a one-liner
(Invoke-WebRequest -Uri https://outlook.office365.com/autodiscover/autodiscover.json/v1.0/gscales@datarumble.com ?Protocol=EWS | ConvertFrom-Json).urlWhen your submitting an Autodiscover request against Office365 if your mailbox is OnPrem and you have HMA configured you will get returned your OnPrem EWS endpoint.
EWS Managed API
So what does this look like in the context of your EWS Managed API code, let first look at the traditional code path for Autodiscover
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013); service.Credentials = new WebCredentials("user1@contoso.com", "password"); service.AutodiscoverUrl("user1@contoso.com", RedirectionUrlValidationCallback);
Here is what it would look like use a Realm Discovery and then a Json Autodiscover and some MSAL code to do the Token Acquisition otherwise following the above logic.
A PowerShell version of the same thing would look like
If you want a library free version that uses Invoke-WebRequest it would look like
Dealing with Token Refresh (Important)
Access tokens by default in Azure are valid for 1 hour, so if your application is going to run for a long period of time or is persistent then you will need to manage token expiration and refresh. If your using one of the Authentication libraries then they can perform this for you automatically however they do rely on you calling their methods before you make any authenticated EWS call. Currently the EWS Managed API doesn't offer a callback to help you integrate easily with an Authentication library (for doing the Token Refresh management) so you will need to come up with your own method of doing this (eg a simple method to check the token before any operation could be used). Or you can modify the EWS Managed API source to integrate your own callback eg a good place to look is PrepareWebRequest in https://github.com/OfficeDev/ews-managed-api/blob/70bde052e5f84b6fee3a678d2db5335dc2d72fc3/Credentials/OAuthCredentials.cs . The good thing about modifying the source is that you fix the issue for any operation that you code will do now and into the future.