Hi Joe,
Here's some guidance.
Please not that settings described are for testing purpose only and are not for production, where more restrictions should be applied according to your company policy(ies).
Microsoft supports since July 2023, a new OAuth flow: credentials flow.
It does not require anymore a user/password pair and allows to use an application for sending mail on "behalf of".
Read this article to get more insights:
Some extra steps are required to allow the application to access user mail boxes. All is described with the above article.
Email configuration
Check Integration configuration
- Enable Custom Smtp Server OAuth in Supportability:
Configure Custom Smtp Integration.
Select type Credential Flow
Please note that Integration still requires a user but this requirement is for connecting to SMTP server and check the connection, not to get a token.
SMTP protocol requires a user to connect to.
Assign new permissions
At the minimum, allow application to access a mail box:
Open a power shell window in admin mode:
Load modules:
Install-Module -Name ExchangeOnlineManagement -allowprerelease
Import-module ExchangeOnlineManagement
Connect-ExchangeOnline -Organization
Then type commands:
New-ServicePrincipal -AppId -ObjectId
Get-ServicePrincipal | fl
Add-MailboxPermission -Identity "<user name>" -User -AccessRights FullAccess
$Mailbox = "user name"
Get-MailboxPermission -Identity $Mailbox | Where-Object { ($.AccessRights -eq "FullAccess") -and ($.IsInherited -eq $false) -and -not ($_.User -like "NT AUTHORITY\SELF") } | ft -AutoSize
Example
Assign application access
New-ServicePrincipal -AppId c9fd0428-a094-434d-9dcb-cd86b6838793 -ObjectId 2c85ed7e-ca0f-451a-
9021
-4b49f91be2f1
Get-ServicePrincipal | fl
Add-MailboxPermission -Identity
"AdeleV@614ryb.onmicrosoft.com"
-User 2c85ed7e-ca0f-451a-
9021
-4b49f91be2f1 -AccessRights FullAccess
$Mailbox =
"AdeleV@614ryb.onmicrosoft.com"
Get-MailboxPermission -Identity $Mailbox | Where-Object { ($_.AccessRights -eq
"FullAccess"
) -and ($_.IsInherited -eq $
false
) -and -not ($_.User -like
"NT AUTHORITY\SELF"
) } | ft -AutoSize
Get a Token
Http request
POST https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id={clientId}&scope=https://outlook.office.com/.default&client_secret=
{secretId}
Pay attention to the scope that needs to be https://outlook.office.com/.default
Example
POST https://login.microsoftonline.com/1cf1f047-bc19-45db-9954-adf81926341b/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=28515eac-a167-4485-85ac-f032492b4e92&scope=https://outlook.office.com/.default&client_secret=ZMocbI
Response:
credentials flow response
{
"token_type" : "Bearer" ,
"expires_in" : 3599 ,
"ext_expires_in" : 3599 ,
"access_token": "<JWT>"
}
Test script in Python
Change values accordingly to your env.
Credentials flow
import base64
import pprint
import smtplib
import msal
conf = {
"authority": "https://login.microsoftonline.com/d44bf45a-e2fa-45bc-8c65-cc1aa10fd573",
"client_id": "28515eac-a167-4485-85ac-f032492b4e92",
"client_secret": "ZMocbI",
"scope": [ "https://outlook.office.com/.default" ],
"tenant_id": "1cf1f047-bc19-45db-9954-adf81926341b",
"user_name": "AdeleV@614ryb.onmicrosoft.com"
}
MAIL_TO = "<your email address>"
SMTP_PORT = 587
SMTP_HOST = "smtp.office365.com"
def encode_token_as_string(username, token) -> str:
xoauth2_token = encode_auth_token_as_bytes(username, token)
return str(xoauth2_token, "utf-8")
def encode_auth_token_as_bytes(username, token) -> bytes:
just_a_str = f"user={username}\x01auth=Bearer {token}\x01\x01"
xoauth2_token = base64.b64encode(just_a_str.encode('utf-8'))
return xoauth2_token
username = conf['user_name']
if \_\_name__ == "\_\_main__":
app = msal.ConfidentialClientApplication(
conf['client_id'],
authority=conf['authority'],
client_credential=conf['client_secret']
)
result = app.acquire_token_silent(conf['scope'], account=None)
if not result:
print("No suitable token in cache. Getting a new one.")
result = app.acquire_token_for_client(scopes=conf['scope'])
if "access_token" in result:
print(result['token_type'])
pprint.pprint(result)
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id"))
server = smtplib.SMTP(SMTP_HOST, SMTP_PORT)
server.set_debuglevel(True)
print("Connect")
server.connect(SMTP_HOST, SMTP_PORT)
print("Ehlo")
server.ehlo()
print("StartTLS")
server.starttls()
server.ehlo()
access_token = result['access_token']
print("start XOATUH2")
code, msg = server.docmd("auth", "XOAUTH2 " + encode_token_as_string(username, access_token))
if code != 235:
raise Exception(base64.b64decode(msg.decode()))
print ("Send mail")
message = 'Subject: {}\n\n{}'.format('hello', 'This is a test')
server.sendmail(username, MAIL_TO, message)
print("Done")
server.quit()