Configuring SMTP with Azure OAuth2.0 for Office 365

Configuring SMTP with Azure OAuth2.0 for Office 365

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

  1. Go to: https://portal.azure.com
  2. Navigate to:
    Azure Active Directory → App registrations → Click New registration.
  3. Set the following:
    • Name: SMTP OAuth App
    • Supported account types: Accounts in this organizational directory only
  4. Click Register.

API Permissions

  1. In the app menu, go to API Permissions → Click Add a permission.
  2. Select APIs my organization uses.
  3. Search for Office 365 Exchange Online.
  4. Choose Application permissions.
  5. Add the following permissions:
    • IMAP.AccessAsApp
    • POP.AccessAsApp
    • SMTP.SendAsApp

Grant Admin Consent

  • After adding permissions, click Grant admin consent for [Your Organization].

Set Up Client Secret

  1. Navigate to Certificates & secrets.
  2. Click New client secret.
  3. Enter a description and expiration duration.
  4. Click Add.
  5. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *