As modern security standards evolve, relying on basic authentication for legacy protocols is no longer a viable option. For enterprise environments utilizing Microsoft 365, transition to modern authentication is essential. This guide outlines how to establish a secure email infrastructure by configuring OAuth 2.0 authentication for sending emails through Office 365 SMTP, utilizing Azure Active Directory (Azure AD) and a dedicated service principal.
Azure Console Configuration
Create an App Registration
- Go to: https://portal.azure.com
- Navigate to:
Azure Active Directory → App registrations → Click New registration. - Set the following:
- Name:
SMTP OAuth App - Supported account types: Accounts in this organizational directory only
- Name:
- Click Register.
API Permissions
- In the app menu, go to API Permissions → Click Add a permission.
- Select APIs my organization uses.
- Search for Office 365 Exchange Online.
- Choose Application permissions.
- Add the following permissions:
IMAP.AccessAsAppPOP.AccessAsAppSMTP.SendAsApp
Grant Admin Consent
- After adding permissions, click Grant admin consent for [Your Organization].
Set Up Client Secret
- Navigate to Certificates & secrets.
- Click New client secret.
- Enter a description and expiration duration.
- Click Add.
- Copy the Value (not the Secret ID). Save it securely.
Gather Required Information
From the App Overview and previous steps, collect:
- Tenant ID:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - Client ID:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - Client Secret: Saved from the above step.
- User Email: Must belong to an Office 365 mailbox, e.g.,
user@example.com.
PowerShell Configuration
Install Required Modules
Install-Module -Name AzureAD
Install-Module -Name ExchangeOnlineManagement
Set Variables
$AppId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Azure App ID
$TenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Azure Tenant ID
Connect to Azure AD
Import-Module AzureAD
Connect-AzureAd -TenantId $TenantId
Get Service Principal
$Principal = Get-AzureADServicePrincipal -Filter "AppId eq '$AppId'"
$PrincipalId = $Principal.ObjectId
$DisplayName = "Principal for IMAP/POP3/SMTP"
Connect to Exchange Online
Set-ExecutionPolicy RemoteSigned
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -Organization $TenantId
Create Service Principal Entry in Exchange
New-ServicePrincipal -AppId $AppId -ServiceId $PrincipalId -DisplayName $DisplayName
Assign Mailbox Permissions
Add-MailboxPermission -User $PrincipalId -AccessRights FullAccess -Identity "user@example.com"
Testing with Python
Here is a sample Python script to test SMTP email sending using OAuth2.0 and Office 365:
import base64
import smtplib
from email.mime.text import MIMEText
from msal import ConfidentialClientApplication
# OAuth2 configuration
tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client_secret = "your-client-secret"
# Sender and recipient
user_email = "user@example.com"
recipient_email = "recipient@example.com"
# OAuth2 token request
authority = f"[https://login.microsoftonline.com/](https://login.microsoftonline.com/){tenant_id}"
scope = ["[https://outlook.office365.com/.default](https://outlook.office365.com/.default)"]
app = ConfidentialClientApplication(
client_id,
authority=authority,
client_credential=client_secret,
)
result = app.acquire_token_for_client(scopes=scope)
if "access_token" not in result:
print("Failed to acquire token:")
print(result.get("error_description"))
exit(1)
access_token = result["access_token"]
# Create email
msg = MIMEText("Hello, this is a test email sent using Office 365 SMTP with OAuth2!")
msg["Subject"] = "Test Email - OAuth2 SMTP"
msg["From"] = user_email
msg["To"] = recipient_email
# Build XOAUTH2 string
auth_string = f"user={user_email}\x01auth=Bearer {access_token}\x01\x01"
auth_b64 = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
# Send email
try:
with smtplib.SMTP("smtp.office365.com", 587) as server:
server.set_debuglevel(1)
server.ehlo("example.com")
if server.has_extn('starttls'):
server.starttls()
server.ehlo("example.com")
else:
raise Exception("STARTTLS not supported by server.")
server.docmd("AUTH", "XOAUTH2 " + auth_b64)
server.sendmail(user_email, [recipient_email], msg.as_string())
print("✅ Email sent successfully!")
except smtplib.SMTPAuthenticationError as e:
print("❌ Authentication failed:")
print(e.smtp_error.decode())
except Exception as e:
print("❌ Failed to send email:")
print(str(e))
Conclusion
By migrating from basic authentication to Azure OAuth 2.0, you significantly enhance the security posture of your automated email workflows. Implementing application-level permissions via service principals ensures that your programmatic connections adhere to the principle of least privilege while maintaining seamless integration with Office 365. Validate this configuration in a test environment before deploying to production systems.
