Access Office 365 Exchange Online Mailbox with Client Credential Flow

As of this writing, Microsoft has disabled the basic authentication to Exchange Online, the alternate more secure method to authenticate and access Exchange Online is via Client Credential flow using OAuth token. OAuth (OIDC) Client Credential flow is typically used for background (eg. windows services or daemon) process without interaction with user.

Microsoft provides different APIs to access Exchange Online. To authenticate against these APIs, we need OAuth access token with appropriate permissions when invoking these APIs. And this type of access token is obtained from Azure AD.

A little background, in order to access Microsoft resource eg. Azure, Exchange Online, SharePoint via Microsoft APIs, the entry point via an identity (or service principal). In this case, it would be by creating an Azure AD app registration.

In Azure AD, an API is granted either with delegated permission or application permission. When using Client Credential flow in Azure AD, the access token is acquired as application identity with only the application permission (delegated permission is not used since there is no user). The scope used during access token request is with *./default scope, for example or depending on which API you are using.

To configure the access restriction of the application, we will need the Exchange Online PowerShell module (ExchangeOnlineManagement). It is the Microsoft Exchange Online administrative interface to Microsoft Exchange Online. To install the PowerShell module,
Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser
Connect to the organization Microsoft Exchange Online, enter the credential in the interactive prompt
Depending if your account has the appropriate role in the organization eg. global admin, exchange admin, etc. it appears to subsequently downloads dynamic module to user path like this, C:\Users\abc\AppData\Local\Temp\tmpEXO_xyzabc10.a12\ where the module seems to contain similar cmdlets like the ones in PowerShell module.

To locate the dynamic module, you can lookup some cmdlet eg.
Get-Command Get-ServicePrincipal
With all the tools and some background covered, here are the two examples of using OAuth to access Office 365 Exchange Online mailbox. One using Legacy Office 365 Exchange Online API to access and read mails of a mailbox via IMAP/POP, and another using Graph API to send mail as the user. Please be aware these are not the only ways, but simply an example. Microsoft provide several other ways to achieve to read and send mails with OAuth.

In order to access the APIs, we need a valid access token with appropriate permission (scope) from Azure AD. First we need to create a application service principal via app registration.

Login to Azure portal, go to Azure Active Directory. Select app registration and select new registration.
Since the application will be using client credential flow, the default option without any redirect URI is sufficient.
Next we will need to create a Client Secret. Select certificates & secrets on the left menu and click on Client secrets and create a new client secret. Keep the Client Secret for later use.
Next we need to grant API permission to the application. Select API Permissions on the left menu, and add a permission.
For Graph API, select Microsoft Graph, click on Application Permissions.
Search for mail, and check Mail.Send. Notice that the permission will allow the application to send mail as any user. We will restrict its access via application access policy described later.
For Office 365 Exchange Online API, select APIs my organization uses tab, search for office and select Office 365 Exchange Online. click on Application Permissions.
Search for and check IMAP.AccessAsApp (if you need to access via POP, check POP.AccessAsApp).
Grant admin consent to the permissions added.
We need to get the ApplicationID (ClientID) and ObjectID of the application service principal for later use. Go to Enterprise Applications on the left menu
Search for the application and note down the ObjectID and ApplicationID.
The app registration is now completed. If needed, you can verify if the access token could be obtained. It is important to know ahead which API you are trying to invoke so you can obtain an access token with correct audience (aud claim in the JWT). For Graph API, use as the scope; And for Office 365 Exchange Online API, use as the scope. Here is an example acquiring an access token for Graph API usage.
$tenantid = 'your tenant id'
$clientid = 'your app client id'
$clientsecret = 'your app client secret'

$token = Invoke-RestMethod -Method Post -ContentType 'application/x-www-form-urlencoded' -Uri "$tenantid/oauth2/v2.0/token" -Body @{
    client_id = $clientid
    client_secret = $clientsecret
    scope = ''
    grant_type = 'client_credentials'

You can copy/paste to view the decoded access token (JWT) here. See the below example of the aud claim and scope with Mail.Send role (permission) configured earlier.
Legacy Office 365 Exchange Online APIs
In the API permission, we granted IMAP.AccessAsApp or POP.AccessAsApp access to the application service principal. This allows the application to access IMAP or POP. However, the application can't access any mailbox yet. And to allow it to access specific mailbox, we first need to add the service principal in exchange online and then grant that service principal permission (delegation) to the mailbox.

Create an exchange service principal. Use the AppId and ObjectId from the Azure AD application registration.
New-ServicePrincipal -AppId c33aa7**-****-****-****-******ffca0d -ServiceId c21b49**-****-****-****-******f33f47 -Organization 'tenant id' -DisplayName mailtest
To grant permission for service principal to a mailbox (add delegation),
Add-MailboxPermission -Identity "" -User c21b49**-****-****-****-******f33f47 -AccessRights FullAccess
To check the existing permission (delegation) on a mailbox,
Get-MailboxPermission -Identity ""
More information to configure for IMAP/POP can be found here.

To verify the IMAP access with the application, you can use this PowerShell script written by one of the Microsoft employee for verification.
$tenantid = 'tenant id'
$clientid = 'c21b49**-****-****-****-******f33f47'
$clientsecret = '***************************'
$maibox = ''

.\Get-IMAPAccessToken.ps1 -tenantID $tenantid -clientId $cliendid -clientsecret $clientsecret -targetMailbox $mailbox

Graph API
For Graph API, understand that granting API Application Permission usually allow it access to all tenant resources. For example, Mail.Send permission allows it to send mail as any user; Mail.Read permission allows it to read mail in all mailboxes. To restrict it to specific mailbox / security group, creates an Application Access Policy.

Note, as of this writing, if there is no application access policy existed, the get application access policy cmdlet returns an error.
Write-ErrorMessage : Ex6F9304|Microsoft.Exchange.Configuration.Tasks.ManagementObjectNotFoundException|The operation couldn't be performed because object ',OU=Microsoft Exchange Hosted Organizations,DC=ABCDE123456,DC=PROD,DC=OUTLOOK,DC=COM\*' couldn't be found on 'AABBCC112233000.ABCDE123456.PROD.OUTLOOK.COM'. At C:\Users\abc\AppData\Local\Temp\tmpEXO_xyzabc10.a12\tmpEXO_xyzabc10.a12.psm1:1113 char:13 + Write-ErrorMessage $ErrorObject $IsFromBatchingRequest + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Get-ApplicationAccessPolicy], ManagementObjectNotFoundException + FullyQualifiedErrorId : [Server=ABCDEFGHIJ123,RequestId=abcd-efgh-3e96-e79a-123456789012,TimeStamp=Sat, 31 S ep 2022 25:02:47 GMT],Write-ErrorMessage

To add application access policy to restrict application access to certain mailbox or security group, use the AppId during the Azure AD application registration. The PolicyScopeGroupId is the mailbox or security group that can be only access by the app.
New-ApplicationAccessPolicy -AccessRight RestrictAccess -AppId c33aa7**-****-****-****-******ffca0d -PolicyScopeGroupId -Description "restrict mailtest access"
To test the policy against the restricted account or/and other account, the AccessCheckResult returns if the effective access is denied or granted.
Test-ApplicationAccessPolicy -AppId c33aa7**-****-****-****-******ffca0d -Identity
From the experience, even thought the test command responded the testing result of the policy, in practical the policy could take more than 1 hour to be effective. From personal experience, it takes about an hour.

To list all existing application access policy,
Please note that the Application Access Policy only applicable to certain Microsoft Graph application permission. As of the time of writing,


Exchange Web Services permission scope: full_access_as_app.

More information can be found here.

No comments:

Post a Comment