In order to authenticate with OAUTH2 you need an existing app registration in Azure. I am assuming you already have this set up.
Add Exchange API permissions to the application
- From within your app registration page, click API permissions along the left menu.
- Check if the Configured permissions section has the Office 365 Exchange Online API. If it does, you can skip to the next section:

- Click the Add a permission button above the table. Select the APIs my organization uses tab and search for Office 365 Exchange Online:

- Select Application permissions for the type, enable the IMAP.AccessAsApp permission. Then click the Add permissions button.

NOTE: An admin may need to approve the permission. They also may need to set up the Service Principal (there is one unclear step to be aware of).
Request access token
- From within your app registration page, click Certificates & secrets along the left menu.
- Select the Client Secrets tab and click the New client secret button. Fill out the form and click Add.
- Copy the Value for the secret. Click Overview in the left menu and copy the Application (client) ID and Directory (tenant) ID. Use these variables in your script to make a POST request to the OAUTH2 endpoint:
from urllib.parse import urlencode
import requests
tenant = "Directory (tenant) ID"
url = f"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
payload = urlencode({
"client_id": "Application (client) ID",
"client_secret": "Secret Value",
"scope": "https://outlook.com/.default",
"grant_type": "client_credentials"
})
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
response = requests.request("POST", url, headers=headers, data=payload)
access_token = response.json()["access_token"]
NOTE: Scope is important! I had to use https://outlook.com/.default
. I've seen some documentation use https://graph.microsoft.com/.default
or https://ps.outlook.com/.default
, neither of which have access to authenticating with the IMAP server.
Use access token to authenticate
Change the imap.login
call to imap.authenticate
:
imap.authenticate(
"XOAUTH2",
lambda _: f"user={email}\1auth=Bearer {access_token}\1\1".encode()
)
The second parameter expects bytes, so I encode
the formatted string.