1

Recently Microsoft has deprecated support for basic authentication to access Office 365 email accounts. Like some other developers, we now have an application that is now broken because of the change.

From what I can tell, the code below should get authentication done (i do get a token back in result), but I am unable to find out what callable object should be the second argument for the imaplib.authenticate function. Can someone help please?

import imaplib
import msal

client = '****-****-*-***'
tenant = '****-****-*-***'
secret_value = '****-****-*-***'
secret_key = '****-****-*-***'

server = 'outlook.office365.com'

app = msal.ConfidentialClientApplication(client, authority=f'https://login.microsoftonline.com/{tenant}', client_credential=secret_value)
result = app.acquire_token_for_client(scopes=['https://graph.microsoft.com/.default'])

conn = imaplib.IMAP4_SSL(server)  
conn.debug = 4

conn.authenticate('XOAUTH2', ??)
Eugene Astafiev
  • 47,483
  • 3
  • 24
  • 45
levraininjaneer
  • 1,157
  • 2
  • 18
  • 38

1 Answers1

1

Your scope is wrong for IMAP on Office365 it needs to be

result = app.acquire_token_for_client(scopes=['https://outlook.office365.com/.default'])

That will ensure your token has the correct audience

You also need to format your token correct as a SASL2 token eg here is a basic working example

import sys  
import base64
import json
import logging
import imaplib
import msal

config = {
    "authority": "https://login.microsoftonline.com/eb8db77e-65e0-4fc3-b967-xxxxxx",
    "client_id": "18bb3888-dad0-4997-96b1-xxxxx",
    "scope": ["https://outlook.office.com/.default"],
    "secret": "_xxxxx",
    "tenant-id": "eb8db77e-65e0-4fc3-b967-xxxxx"
}
app = msal.ConfidentialClientApplication(config['client_id'], authority=config['authority'],
                                             client_credential=config['secret'])
result = app.acquire_token_silent(config["scope"], account=None)

def GenerateOAuth2String(username, access_token):
  auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token)
  return auth_string

if not result:
    logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
    result = app.acquire_token_for_client(scopes=config["scope"])

if "access_token" in result:
    user = 'gscales@bbbb.onmicrosoft.com'
    server = 'outlook.office365.com'
    conn = imaplib.IMAP4_SSL(server)  
    conn.debug = 4
    conn.authenticate('XOAUTH2', lambda x: GenerateOAuth2String(user, result['access_token']))

else:
    print(result.get("error"))
    print(result.get("error_description"))
    print(result.get("correlation_id"))  # You may need this when reporting a bug

If you get errors running the above its most likely you haven't registered the service principal in Exchange or granted permissions to the Mailbox.

eg from https://techcommunity.microsoft.com/t5/exchange-team-blog/announcing-oauth-2-0-support-for-imap-and-smtp-auth-protocols-in/bc-p/1544725/highlight/true#M28589

New-ServicePrincipal -AppId <APPLICATION_ID> -ServiceId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]

and

Add-MailboxPermission -Identity "john.smith@contoso.com" -User <SERVICE_PRINCIPAL_ID> -AccessRights FullAccess

Unlike the Graph or EWS when you use the client credentials flow in IMAP you don't get access to every mailbox in the tenant by default it must be explicitly granted.

Glen Scales
  • 20,495
  • 1
  • 20
  • 23
  • thanks! We've tried to align with all of your advice, but we are sill getting `imaplib.IMAP4.error: AUTHENTICATE failed.` – levraininjaneer Oct 12 '22 at 12:00
  • 1
    Me too.. absolute hair pull... Can get the tokens fine but also receiving `imaplib.error: AUTHENTICATE failed.` - Service principal is registered and mailbox has all permissions... I'm at a loss – henry434 Oct 12 '22 at 15:15
  • Make sure IMAP is enabled at the user level in the Azure Portal – Glen Scales Oct 20 '22 at 22:57