Skip to main content

Using the MSAL (Microsoft Authentication Library) in EWS with Office365

Last July Microsoft announced here they would be disabling basic authentication in EWS on October 13 2020 which is now a little over a year away. Given the amount of time that has passed since the announcement any line of business applications or third party applications that you use that had been using Basic authentication should have been modified or upgraded to support using oAuth. If this isn't the case the time to take action is now.

When you need to migrate a .NET app or script you have using EWS and basic Authentication you have two Authentication libraries you can choose from

  1. ADAL - Azure AD Authentication Library (uses the v1 Azure AD Endpoint)
  2. MSAL - Microsoft Authentication Library (uses the v2 Microsoft Identity Platform Endpoint)
the most common library you will come across in use is the ADAL libraries because its been around the longest, has good support across a number of languages and allows complex authentications scenarios with support for SAML etc. The MSAL is the latest and greatest in terms of its support for oAuth2 standards and is where Microsoft are investing their future development efforts. A good primer for understanding the difference in terms of the Tokens that both of these endpoint generate is to read https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens

So which should you choose ? If your using PowerShell then the ADAL is the easiest to use and there are a lot of good examples for this like. However from a long term point of view using MSAL library can be a better choice as its going to offer more supportability (new features etc) going forward as long as you don't fall into one of  the restrictions described in https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Adal-to-Msal

In this post I'm going to look at using the MSAL library with EWS to access Mailboxes in Exchange Online. 

Scopes

One of the biggest differences when it comes to coding between the libraries with ADAL you specify the resource your going to use eg "https://outlook.office365.com" and with the MASL you specific the scopes you are going to use. With EWS its relatively simple in that  there are only two scopes (EWS doesn't allow you to constrain your access to different mailbox item types) which you would first need to allow in your Application registration which can be found in the Supported Legacy API's section of the application registration(make sure you scroll right to the bottom)

Delegated Permissions


Application Permissions (where you going to use AppOnly Tokens)

.default Scope

For v.1 apps you can get all the static scopes configured in an application using the .default scope so for ews that would look something like https://outlook.office365.com/.default . When your using App Only tokens this becomes important.


App registration and Consent

One of the advantages of the MSAL library is dynamic consent which for EWS because in practice your only going to be using one scope it doesn't have much use. However if your also going to be using other workloads you maybe able to take advantage of that feature. For the app registration you need to use the v2 Endpoint registration process (which is the default now in the Azure portal) see https://docs.microsoft.com/en-us/graph/auth-register-app-v2. This also makes it easy to handle the consent within a tenant.

Getting down to coding

In the ADAL there was only one single class called the AuthenticationContext which you used to request tokens. In the MSAL you have the PublicClientApplication (which you use for standard user authentication) and ConfidentialClientApp which gets used for AppOnly tokens and On-Behalf-Of flow.

Endpoints 

With the v2 Endpoint you have the option of allowing
  1. common
  2. organizations
  3. consumers
  4. Tenant specific (Guid or Name)
For EWS you generally always want to use the Tenant specific endpoint which means its best to either dynamically get the TenantId for the tenant your targeting or hard code it . eg you can get the TenantId need with 3 lines of C#


string domainName = "datarumble.com";
HttpClient Client = new HttpClient();
var TenantId = ((dynamic)JsonConvert.DeserializeObject(Client.GetAsync("https://login.microsoftonline.com/" + domainName + "/v2.0/.well-known/openid-configuration")
     .Result.Content.ReadAsStringAsync().Result))
    .authorization_endpoint.ToString().Split('/')[3];
In PowerShell you can do it with

$TenantId = (Invoke-WebRequest https://login.windows.net/datarumble.com/v2.0/.well-known/openid-configuration | ConvertFrom-Json).token_endpoint.Split('/')[3]

Delegate Authentication in EWS with MSAL and the EWS Managed API

This generally is the most common way of using EWS where your authenticating as a standard User and then accessing a Mailbox. If its a shared Mailbox then access will need to be have granted via Add-MailboxFolderPermission or you using EWS Impersonation

This is the simplest C# example of an Auth using the MSAL library in a Console app to logon (the currently logged on user).

 string MailboxName = "gscales@datarumble.com";
 string scope = "https://outlook.office.com/EWS.AccessAsUser.All";
 string redirectUri = "msal9d5d77a6-fe09-473e-8931-958f15f1a96b://auth";
 string domainName = "datarumble.com";

 HttpClient Client = new HttpClient();
 var TenantId = ((dynamic)JsonConvert.DeserializeObject(Client.GetAsync("https://login.microsoftonline.com/" + domainName + "/v2.0/.well-known/openid-configuration")
  .Result.Content.ReadAsStringAsync().Result))
  .authorization_endpoint.ToString().Split('/')[3];

 PublicClientApplicationBuilder pcaConfig = PublicClientApplicationBuilder.Create("9d5d77a6-fe09-473e-8931-958f15f1a96b")      
  .WithTenantId(TenantId);
   
 pcaConfig.WithRedirectUri(redirectUri);
 var TokenResult = pcaConfig.Build().AcquireTokenInteractive(new[] { scope })
  .WithPrompt(Prompt.Never)
  .WithLoginHint(MailboxName).ExecuteAsync().Result;

 ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2016);
 service.Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
 service.Credentials = new OAuthCredentials(TokenResult.AccessToken);
 service.HttpHeaders.Add("X-AnchorMailbox", MailboxName);

 Folder Inbox = Folder.Bind(service, WellKnownFolderName.Inbox);


AppOnly Tokens

This is where your Application is authenticating using a App Secret or SSL certificate, after this your App will get full access to all Mailboxes in a tenant (it important to not that the scoping feature https://docs.microsoft.com/en-us/graph/auth-limit-mailbox-access doesn't work with the EWS so you need to be using the Graph or Outlook api).

 string clientId = "9d5d77a6-fe09-473e-8931-958f15f1a96b";
 string clientSecret = "xxxx";
 string mailboxName = "gscales@datarumble.com";
 string redirectUri = "msal9d5d77a6-fe09-473e-8931-958f15f1a96b://auth";
 string domainName = "datarumble.com";
 string scope = "https://outlook.office365.com/.default";

 HttpClient Client = new HttpClient();
 var TenantId = ((dynamic)JsonConvert.DeserializeObject(Client.GetAsync("https://login.microsoftonline.com/" + domainName + "/v2.0/.well-known/openid-configuration")
  .Result.Content.ReadAsStringAsync().Result))
  .authorization_endpoint.ToString().Split('/')[3];

 IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId)
  .WithClientSecret(clientSecret)
  .WithTenantId(TenantId)
  .WithRedirectUri(redirectUri)
  .Build();

  
 var TokenResult = app.AcquireTokenForClient(new[] { scope }).ExecuteAsync().Result;
 ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2016);
 service.Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
 service.Credentials = new OAuthCredentials(TokenResult.AccessToken);
 service.HttpHeaders.Add("X-AnchorMailbox", mailboxName);
 service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, mailboxName);
 Folder Inbox = Folder.Bind(service, new FolderId(WellKnownFolderName.Inbox, mailboxName));


Token Refresh

One of the big things missing in the EWS Managed API is a callback before each request that checks for an expired Access Token. Because tokens are only valid for 1 hour if you have a long running process like a migration/export or data analysis then you need to make sure that you have some provision in your code to track the expiry of the access token and the refresh the token when needed.

Doing this in PowerShell

If your using PowerShell you can use the same code as above as long as import the MSAL library dll into your session

Some simple auth examples for this would be

 Delegate Authentication 


$MailboxName = "gscales@datarumble.com";
$ClientId = "9d5d77a6-fe09-473e-8931-958f15f1a96b"
$scope = "https://outlook.office.com/EWS.AccessAsUser.All";
$redirectUri = "msal9d5d77a6-fe09-473e-8931-958f15f1a96b://auth";
$domainName = "datarumble.com";
$Scopes = New-Object System.Collections.Generic.List[string]
$Scopes.Add($Scope)
$TenantId = (Invoke-WebRequest https://login.windows.net/datarumble.com/v2.0/.well-known/openid-configuration | ConvertFrom-Json).token_endpoint.Split('/')[3]
$pcaConfig = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($ClientId).WithTenantId($TenantId).WithRedirectUri($redirectUri)
$TokenResult = $pcaConfig.Build().AcquireTokenInteractive($Scopes).WithPrompt([Microsoft.Identity.Client.Prompt]::Never).WithLoginHint($MailboxName).ExecuteAsync().Result;

AppOnly Token


$ClientId = "9d5d77a6-fe09-473e-8931-958f15f1a96b"
$MailboxName = "gscales@datarumble.com"
$RedirectUri = "msal9d5d77a6-fe09-473e-8931-958f15f1a96b://auth"
$ClientSecret = "xxx";
$Scope = "https://outlook.office365.com/.default"
$TenantId = (Invoke-WebRequest https://login.windows.net/datarumble.com/v2.0/.well-known/openid-configuration | ConvertFrom-Json).token_endpoint.Split('/')[3]
$app =  [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($ClientId).WithClientSecret($ClientSecret).WithTenantId($TenantId).WithRedirectUri($RedirectUri).Build()
$Scopes = New-Object System.Collections.Generic.List[string]
$Scopes.Add($Scope)
$TokenResult = $app.AcquireTokenForClient($Scopes).ExecuteAsync().Result;





Popular posts from this blog

Testing and Sending email via SMTP using Opportunistic TLS and oAuth in Office365 with PowerShell

As well as EWS and Remote PowerShell (RPS) other mail protocols POP3, IMAP and SMTP have had OAuth authentication enabled in Exchange Online (Official announcement here ). A while ago I created  this script that used Opportunistic TLS to perform a Telnet style test against a SMTP server using SMTP AUTH. Now that oAuth authentication has been enabled in office365 I've updated this script to be able to use oAuth instead of SMTP Auth to test against Office365. I've also included a function to actually send a Message. Token Acquisition  To Send a Mail using oAuth you first need to get an Access token from Azure AD there are plenty of ways of doing this in PowerShell. You could use a library like MSAL or ADAL (just google your favoured method) or use a library less approach which I've included with this script . Whatever way you do this you need to make sure that your application registration  https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-

How to test SMTP using Opportunistic TLS with Powershell and grab the public certificate a SMTP server is using

Most email services these day employ Opportunistic TLS when trying to send Messages which means that wherever possible the Messages will be encrypted rather then the plain text legacy of SMTP.  This method was defined in RFC 3207 "SMTP Service Extension for Secure SMTP over Transport Layer Security" and  there's a quite a good explanation of Opportunistic TLS on Wikipedia  https://en.wikipedia.org/wiki/Opportunistic_TLS .  This is used for both Server to Server (eg MTA to MTA) and Client to server (Eg a Message client like Outlook which acts as a MSA) the later being generally Authenticated. Basically it allows you to have a normal plain text SMTP conversation that is then upgraded to TLS using the STARTTLS verb. Not all servers will support this verb so if its not supported then a message is just sent as Plain text. TLS relies on PKI certificates and the administrative issue s that come around certificate management like expired certificates which is why I wrote th

The MailboxConcurrency limit and using Batching in the Microsoft Graph API

If your getting an error such as Application is over its MailboxConcurrency limit while using the Microsoft Graph API this post may help you understand why. Background   The Mailbox  concurrency limit when your using the Graph API is 4 as per https://docs.microsoft.com/en-us/graph/throttling#outlook-service-limits . This is evaluated for each app ID and mailbox combination so this means you can have different apps running under the same credentials and the poor behavior of one won't cause the other to be throttled. If you compared that to EWS you could have up to 27 concurrent connections but they are shared across all apps on a first come first served basis. Batching Batching in the Graph API is a way of combining multiple requests into a single HTTP request. Batching in the Exchange Mail API's EWS and MAPI has been around for a long time and its common, for email Apps to process large numbers of smaller items for a variety of reasons.  Batching in the Graph is limited to a m
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.