The end is near (for legacy auth)!

Microsoft announced first the deprecation of Basic Authentication for Exchange Online and EWS protocol starting Oct. 13, 2020 here.

Note: At this time this affected ONLY the protocol EWS for mailboxes on Exchange Online!

Later it was announced that this also happens for other protocols like Exchange Active Sync (EAS), POP, IMAP and PowerShell at the same time here, in order to improve security.

Looking at the protocols, you might wonder about REST. This was announced for REST API v1.0 shortly after the announcement for EWS here and highlighted again here.

With this, there is no doubt that Basic Authentication is dead for Exchange Online and Microsoft Graph and every vendor should look into alternatives for authentication AND also update their products. There are still way too many products without support of Modern Auth.

The deprecation of Basic Authentication raises a few questions:

  • How can I access mailboxes with my service account?
  • My application needs access to all or only a subset of calendars. How can I securely configure this?
  • I need to Send-As or Send-on-Behalf of recipients’ e-mail addresses. What do I have to configure?

In this post I’m trying to cover some scenarios and try to explain advantages and disadvantages.

Note: This article is ONLY covering OAuth and Exchange Online! I assume you’re using a Bearer token for authentication in your request!

Content and quick links:

Exchange Web Services (EWS)

REST (MS Graph/Outlook API)

Which endpoint should I use?

Real life examples

Common errors

Decision maker helper

Exchange Web Services (EWS)

Exchange Web Service (EWS) is by far the most common used protocol, when it comes to processing mailbox data. As of today, it gives you high degree of functionality. This protocol comes with two modes of accessing mailboxes:

Delegation

In this mode the application will access a mailbox to which it was granted access.

Read more about it here.

ApplicationImpersonation

In this mode the application will impersonate and act in the name of the accessed mailbox.

Read more about it here.

Note: These access modes have nothing to do with authentication and are not comparable with OAuth permission types!

What is the difference?

Delegation will always have a user, service account involved (also known as Alternative Service Account). Usually you would grant such an account FullAccess to mailboxes or at least to folders within a mailbox, which will be processed (e.g.: Calendar). Besides this the ASA will need to have a mailbox assigned (if not you will see errors like ErrorNonExistentMailbox).

AppliactionImpersonation

This is a special permission, which can, but doesn’t need to have an ASA involved. If you have an ASA and grant the account this permission, you can also scope the permissions by creating a recipient scope using the Cmdlet New-ManagementScope and New-ManagementRoleAssignment. More info about this can be found on Docs “Configure the ApplicationImpersonation role”.

In OAuth terms the following permissions are available:

Delegated

https://outlook.office365.com/EWS.AccessAsUser.All

https://graph.microsoft.com/EWS.AccessAsUser.All

Application

https://outlook.office365.com/full_access_as_app

Note: There is no Application permission available for Microsoft Graph! In terms of scoping: it is only possible, when you either define a user or a group and create and use the previously mentioned Exchange Cmdlets. This means scoping is only available when using an ASA. Application permissions means access to ALL mailboxes.

REST (MS Graph/Outlook API)

REST API is the newer protocol and meanwhile used by almost any client of C2R suite. It has not yet the full functionality as EWS, but there are some functionalities, which are only available in REST and not in EWS (e.g.: AutodiscoverV2, mailboxSettings).

There are no modes like in EWS. The are only the OAuth permission types Delegated and Application permissions. There are many permissions available for resources Microsoft Graph and Exchange Online, but the most important for accessing mailboxes are the following ones:

  • Mail.Read
  • Mail.ReadWrite
  • Mail.Send
  • MailboxSettings.Read
  • MailboxSettings.ReadWrite
  • Calendars.Read
  • Calendars.ReadWrite
  • Contacts.Read
  • Contacts.ReadWrite

For requesting permissions for the resource Microsoft Graph, you would prepend https://graph.microsoft.com/ and for Exchange Online https://outlook.office365.com/.

Example (Mail.Read):

Microsoft Graph: https://graph.microsoft.com/Mail.Read

Exchange Online: https://outlook.office365.com/Mail.Read

These permissions are not only the most important ones, these are also the ones, which can be scoped to a subset of mailboxes in case Application permissions have been granted.

Remember: When Application permissions have been granted, the app has access to ALL mailboxes in Exchange Online! The only way of limiting access, is creating an ApplicationAccessPolicy. You can read more about this here on Docs:

Scoping application permissions to specific Exchange Online mailboxes

Or on EHLO blog:

https://techcommunity.microsoft.com/t5/exchange-team-blog/scoping-microsoft-graph-application-permissions-to-specific/ba-p/671881

Which endpoint should I use?

As we have two different endpoints, Microsoft Graph and Exchange Online, it’s a valid question, which one should be used. The answer is complicated as this highly depends on your design or already written code. There are some technical differences, which must be read and understood as the response could be different than expected!

Have a close look at the outlined differences on Docs:

https://docs.microsoft.com/outlook/rest/compare-graph#feature-differences

Real life examples

In all examples you need to have an app registered in AAD and granted the proper permissions. For Application permissions you also need to make sure Admin Consent was granted. For testing purposes, I registered a single application with the following permissions:

Exchange Online

Microsoft Graph

Note: For security reasons and bets practices, I highly recommend not mixing Delegated and Application permissions in a single app. Rather register dedicated apps for different use!

The functions Test-SendMail and Get-CalendarEvents, I used in the examples, can be found on GitHub here. Get-AccessToken can also be found here.

Scenario#1

Sending e-mails On Behalf-of or Send-As of a mailbox.

EWS Delegated

Requirements:

  • Dedicated ASA, which has an Exchange Online license assigned (otherwise the user won’t be visible in the workload)
  • Grant On Behalf-of or Send-As permission the ASA for the recipient (e.g.: Set-Mailbox -Identity <recipient> -GrantSendOnBehalfTo <ASA>, Add-RecipientPermission -Identity <recipient> -Trustee <ASA> -AccessRights ‘SendAs’
$ASADelegated = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    RedirectUri = 'https://localhost/45d78292-…-b5a45b62bc82'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https://outlook.office365.com/'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ASADelegated -ParseToken

$ASADelegatedParamsEWS = @{
    EmailAddress = 'asa@contoso.com'
    FromAddress = 'InTheNameOfMailbox@contoso.com'
    Recipients = 'ingo@contoso.de','bob@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    Verbose = $true
}

#sending e-mail using EWS
Test-SendMail @ASADelegatedParamsEWS

EWS ApplicationImpersonation (with ASA)

Requirements:

  • Dedicated ASA, which has an Exchange Online license assigned (otherwise the user won’t be visible in the workload)
  • The ASA has been assigned the role ApplicationImpersonation via RBAC (read Docs here)
$ASADelegated = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    RedirectUri = 'https://localhost/45d78292-…-b5a45b62bc82'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https://outlook.office365.com/'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ASADelegated -ParseToken

$ASADelegatedParamsEWSImpersonated = @{
    EmailAddress = 'asa@contoso.com'
    FromAddress = 'InTheNameOfMailbox@contoso.com'
    Recipients = 'ingo@contoso.de','bob@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    Verbose = $true
    Impersonate = $true 
}

#sending e-mail using EWS
Test-SendMail @ASADelegatedParamsEWSImpersonated

EWS ApplicationImpersonation (with ASA and scoped)

Requirements:

  • Dedicated ASA, which has an Exchange Online license assigned (otherwise the user won’t be visible in the workload)
  • Custom management scope (for example created with New-ManagementScope)
  • The ASA has been assigned the role ApplicationImpersonation via RBAC (read Docs here)

This is the same as the previous one. The only difference is that a management scope was created and configured when ApplicationImpersonation permission was assigned.

EWS ApplicationImpersonation (with ClientCredentials)

Requirements:

  • A registered app with OAuth ClientCredentials (Note: ClientCredentials are NOT the same as username and password. It can be either a client secret or a certificate. Read more about Client Credential flow here!)
  • Make sure you have ExchangeImpersonation element set in your request!
  • An admin consent for the tenant to the permissions
$ClientCred = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    ClientSecret = 'hDItcI=W…u8rEIv5D:'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https://outlook.office365.com/'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ClientCred -ParseToken

$ClientCredImpersonate = @{
    EmailAddress = 'InTheNameOfMailbox@contoso.com'
    Recipients = 'ingo@contoso.de','bob@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    Verbose = $true
    Impersonate = $true 
}

#sending e-mail using EWS
Test-SendMail @ClientCredImpersonate

Note: There is no difference to the previous ApplicationImpersonation tasks, besides the fact that you do NOT need any ASA. Downside of this is the fact that there is no technical solution of scoping.

REST Delegated

Requirements:

  • Dedicated ASA, which has an Exchange Online license assigned (otherwise the user won’t be visible in the workload)
  • Grant On Behalf-of or Send-As permission the ASA for the recipient (e.g.: Set-Mailbox -Identity <recipient> -GrantSendOnBehalfTo <ASA>, Add-RecipientPermission -Identity <recipient> -Trustee <ASA> -AccessRights ‘SendAs’
$ASADelegated = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    RedirectUri = 'https://localhost/45d78292-…-b5a45b62bc82'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https://graph.microsoft.com'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ASADelegated -ParseToken

$ASADelegatedParamsREST = @{
    EmailAddress = 'asa@contoso.com'
    FromAddress = 'InTheNameOfMailbox@contoso.com'
    Recipients = 'ingo@contoso.de','bob@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    Verbose = $true
}

#sending e-mail using REST
Test-SendMail @ASADelegatedParamsREST

REST Application

Requirements:

  • A registered app with OAuth ClientCredentials (Note: ClientCredentials are NOT the same as username and password. It can be either a client secret or a certificate. Read more about Client Credential flow here!) and Application permissions
  • An admin consent for the tenant to the permissions
$ClientCred = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    ClientSecret = 'hDItcI=W…u8rEIv5D:'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https:// graph.microsoft.com/'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ClientCred -ParseToken
$ClientCredREST = @{
    EmailAddress = 'InTheNameOfMailbox@contoso.com'
    Recipients = 'ingo@contoso.de','bob@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    Verbose = $true
    UseRest = $true 
}

#sending e-mail using REST
Test-SendMail @ClientCredREST

REST Application (scoped)

This is the same as the previous one REST Application, but in addition an ApplicationAccessPolicy created using Cmdlet New-ApplicationAccessPolicy.

Note: You will be able to acquire an access token, but you will receive an error in the respond from either Microsoft Graph or Exchange Online.

Scenario#2

Accessing and reading calendar items of a mailbox.

EWS Delegated

Requirements:

  • Dedicated ASA, which has an Exchange Online license assigned (otherwise the user won’t be visible in the workload)
  • Either FullAccess to the complete mailbox or permissions to the calendar
$ASADelegated = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    RedirectUri = 'https://localhost/45d78292-…-b5a45b62bc82'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https://outlook.office365.com/'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ASADelegated -ParseToken

$ASADelegatedParamsEWS = @{
    EmailAddress = 'NameOfSharedMailbox@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    UseEWS = $true
    Verbose = $true
}

#get calendar items
Get-CalendarEvents @ASADelegatedParamsEWS

EWS ApplicationImpersonation (with ASA)

Requirements:

  • Dedicated ASA, which has an Exchange Online license assigned (otherwise the user won’t be visible in the workload)
  • The ASA has been assigned the role ApplicationImpersonation via RBAC (read Docs here)
$ASADelegated = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    RedirectUri = 'https://localhost/45d78292-…-b5a45b62bc82'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https://outlook.office365.com/'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ASADelegated -ParseToken

$ASADelegatedParamsEWSImperssonated = @{
    EmailAddress = 'NameOfSharedMailbox@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    UseEWS = $true
    Impersonate = $true
    Verbose = $true
}

#get calendar items
Get-CalendarEvents @ASADelegatedParamsEWSImpersonated

EWS ApplicationImpersonation (with ASA and scoped)

Requirements:

  • Dedicated ASA, which has an Exchange Online license assigned (otherwise the user won’t be visible in the workload)
  • Custom management scope (for example created with New-ManagementScope)
  • The ASA has been assigned the role ApplicationImpersonation via RBAC (read Docs here)

This is the same as the previous one. The only difference is that a management scope was created and configured when ApplicationImpersonation permission was assigned.

EWS ApplicationImpersonation (with ClientCredentials)

Requirements:

  • A registered app with OAuth ClientCredentials (Note: ClientCredentials are NOT the same as username and password. It can be either a client secret or a certificate. Read more about Client Credential flow here!)
  • Make sure you have ExchangeImpersonation element set in your request!
  • An admin consent for the tenant to the permissions
$ClientCred = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    ClientSecret = 'hDItcI=W…u8rEIv5D:'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https://outlook.office365.com/'
}
#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ClientCred -ParseToken

$ClientCredImpersonate = @{
    EmailAddress = 'AnyMailbox@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    UseEWS = $true
    Impersonate = $true 
    Verbose = $true
}

#get calendar items
Get-CalendarEvents @ClientCredImpersonate

REST Delegated

Requirements:

  • Dedicated ASA, which has an Exchange Online license assigned (otherwise the user won’t be visible in the workload)
  • Either FullAccess to the complete mailbox or permissions to the calendar
$ASADelegated = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    RedirectUri = 'https://localhost/45d78292-…-b5a45b62bc82'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https://graph.microsoft.com/'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ASADelegated -ParseToken

$ASADelegatedParamsREST = @{
    EmailAddress = 'NameOfSharedMailbox@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    UseMSGraph = $true
    Verbose = $true
}

#get calendar items
Get-CalendarEvents @ASADelegatedParamsRest

REST Application

  • A registered app with OAuth ClientCredentials (Note: ClientCredentials are NOT the same as username and password. It can be either a client secret or a certificate. Read more about Client Credential flow here!)
  • An admin consent for the tenant to the permissions
$ClientCred = @{
    ClientID = '4228918b-27c0-…-2da25a90ad04'
    ClientSecret = 'hDItcI=W…u8rEIv5D:'
    ClearTokenCache = $true
    Verbose = $true
    Authority = 'https://login.microsoftonline.com/contoso.com'
    Resource = 'https:// graph.microsoft.com/'
}

#requesting OAuth Accesstoken
$token = .'C:\Temp\Get-AccessToken.ps1' @ClientCred -ParseToken
$ClientCredREST = @{
    EmailAddress = 'AnyMailbox@contoso.com'
    AccessToken = $($token[0].CreateAuthorizationHeader())
    UseMSGraph = $true
    Verbose = $true
}

#get calendar items
Get-CalendarEvents @ClientCredREST

REST Application (scoped)

This is the same as the previous one REST Application, but in addition an ApplicationAccessPolicy created using Cmdlet New-ApplicationAccessPolicy.

Note: You will be able to acquire an access token, but you will receive an error in the respond from either Microsoft Graph or Exchange Online.

Scenario#3

Accessing and reading messages in a mailbox is the same as working with calendar items. The code needs to be adjusted and for REST the correct permissions (Mail.*) granted.

Common errors

Error#1

The resource/audience is very important, when acquiring an access token and where you send the request. The resource/audience must match the endpoint you are sending the request to. Otherwise you will receive an error!

“Access token validation failure. Invalid audience.”

Solution:

Use the correct audience, when acquiring an access token.

Error#2

Using EWS with OAuth Application permission, but no ExchangeImpersonation element present:

Possible solution:

Add the missing ExchangeImpersonation element to your SOAP request or when using managed API set Impersonate to $true.

Error#3

You receive an error in EWS or REST when sending e-mail:

“SendOnly cannot be used by a user without a mailbox.  Use SendAndSaveCopy and specify a folder ID in a mailbox to send an item from an account that doesn’t have a mailbox.”

Possible solution:

If you’re using an ASA, you need to grant at least access to the “Sent Items” folder.

Error#4

You receive an error in EWS or REST when sending e-mail:

“The user account which was used to submit this request does not have the right to send mail on behalf of the specified sending account., Cannot submit message.”

Solution:

Grant either Send-As or On Behalf-of permission to ASA.

Error#5

You receive an error while sending an e-mail using REST:

“The specified folder could not be found in the store.”

Possible solution:

Grant either Send-As or On Behalf-of permission to ASA.

Error#6

You receive an error when accessing a calendar using EWS:

Exception calling “Bind” with “2” argument(s): “The specified folder could not be found in the store.”

Using REST:

“code”: “DelegatedCalendarAccessDenied”, “message”: “Access is denied. Check credentials and try again.”

Solution:

Grant access to ASA on calendar folder or FullAccess permission to the mailbox.

Decision maker helper

In order to get an idea of what steps needs to be done, I tried to help with some flowcharts.

EWS

REST

Conclusion

I hope I covered the main scenarios and it helps understanding how to work with mailboxes and objects or items within in Exchange Online using either EWS or REST and authenticate with OAuth.

Please bear in mind that the list of errors is NOT complete. I mentioned the most common ones I stumbled across and its subject to be changed/updated.

3 thoughts on “The end is near (for legacy auth)!

  1. Pingback: Microsoft Graph, Exchange Online and the lack of proper logging | The clueless guy

  2. Hi, I’ve searched high and low and your site has been the best resource so far….Thanks

    I’m struggling a little to understand how I would get an application that will need to send on behalf of users via EWS using an exchange online mail account. I’ve been able to create registered app in azure ad but lost as to what I need to do after that. So how do I get the account to correctly sending on behalf of users.

    The off the shelf application just requires an email account and password but currently fails as the application does not natively support OAuth.

    Sorry if I’ve missed something obvious, I’m very new to this!

    Also what does ‘ASA’ mean? 🙂

    Like

    • Hi uptownboyblog,
      thanks for the laud!
      When it comes to EWS, you have only to change the authentication. You need replace existing auth with [Microsoft.Exchange.WebServices.Data.OAuthCredentials]. If your application doesn’t support OAuth(just give you the ability of Username and Password), you should talk to your vendor.
      ‘ASA’ comes from Alternative Service Account, which is well-known in the area of Exchange. In the end it’s just a service account, which is used for automating stuff.
      Ciao,
      Ingo

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s