Blog · Deliverability

How to Migrate from SMTP Basic Auth to OAuth 2.0 for Microsoft 365

What happened to SMTP basic auth in Exchange Online?

Microsoft started disabling SMTP basic auth in Exchange Online in September 2022, with the process accelerating through 2023 and 2024. The reason is straightforward: basic auth sends a username and password with every connection. If that credentials pair leaks, an attacker has permanent access until you rotate the password. OAuth 2.0 tokens are short-lived, scoped, and revocable individually, which means a leaked token causes limited damage and can be revoked without touching the underlying account credentials.

Not every tenant is affected at the same time. Microsoft is rolling this out in waves by tenant. If you have not yet received an admin notification about basic auth deprecation, you likely will soon. The official deadline for full deprecation has passed for many tenants, but the rollout continues. Even if your tenant has not yet disabled basic auth, the migration is worth doing proactively: basic auth is a single point of failure that OAuth eliminates.

If you run a Linux mail relay using Postfix or Sendmail that authenticates to Exchange Online via basic auth, this article covers exactly what you need to change and where the migration tends to break.

Why OAuth 2.0 for SMTP is different from web app OAuth

OAuth 2.0 in a web app context involves browser redirects, refresh tokens stored server-side, and user-facing consent screens. OAuth for SMTP is different. The protocol uses a mechanism called XOAUTH2, where the SMTP client presents an OAuth 2.0 access token as the password during SMTP authentication. The access token is obtained using the client credentials flow (app-only token) rather than the authorization code flow used by web apps.

For Postfix talking to Exchange Online, the flow looks like this:

1. Your relay server obtains an access token from Microsoft Entra ID (formerly Azure AD) using a client ID and client secret (or certificate).
2. The access token is passed as the password during the SMTP AUTH command using the XOAUTH2 mechanism.
3. Exchange Online validates the token and issues a short-lived access grant for mail relay.
4. The token expires after 1 hour (the default for Microsoft access tokens).
5. Your relay must obtain a fresh token before the old one expires or mail delivery starts failing.

Step 5 is where most teams stumble. If you set up OAuth and walk away, the relay will work for 59 minutes and then silently stop sending. This is the most common post-migration incident.

What you need before you start

Before touching any configuration, gather the following:

  • An Azure subscription with access to Microsoft Entra ID (any tier including free tier works).
  • A registered application in Microsoft Entra ID with SMTP permission granted.
  • A client secret (or certificate) for the app registration.
  • Postfix version 2.10 or later on your relay server (required for SMTP AUTH support).
  • The ability to add an A record or CNAME to your domain DNS for an autodiscover host.
  • Root or sudo access on the relay server.

The Azure App Registration is the starting point. You cannot use an existing basic auth account for OAuth; the authentication method is tied to the application registration, not a user account.

Registering an app in Azure for OAuth SMTP relay

1. In the Microsoft Entra ID admin center, go to Applications > App registrations > New registration.
2. Give it a name like "Postfix Exchange Online Relay." Set supported account types to "Accounts in this organizational directory only" unless you have a multi-tenant scenario.
3. After registration, go to Certificates & secrets > New client secret. Copy the secret value now. You cannot retrieve it after you leave this page.
4. Go to API permissions > Add a permission. Find "Microsoft Graph" and select "Application permissions." Add Mail.Send.
5. Grant admin consent for your organization.
6. Go to Authentication > Add a platform > Mobile and desktop applications. Select the checkbox for "https://login.microsoftonline.com/common/oauth2/nativeclient" as a redirect URI. This is required for the OAuth token flow even though this is a daemon process, not a user app.

One thing that trips people up: the redirect URI is required even for daemon-style app-only token requests because Microsoft Entra ID requires it for the token endpoint. Without it, you will get an AADSTS error when requesting tokens.

Configuring Postfix for OAuth 2.0 with Exchange Online

The Postfix side requires two files: a SASL password file and a TLS configuration that uses OAuth 2.0 as the authentication mechanism.

First, create the SASL password file:


/etc/postfix/sasl_passwd

[smtp.office365.com]:587 your-app-client-id:your-access-token

The access token field will be updated by a token refresh script (see the next section). Initially you will need to generate a test token manually to populate this file.

The main.cf changes:


relayhost = [smtp.office365.com]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_tls_security_options = noanonymous
smtp_tls_security_level = encrypt
smtp_tls_wrappermode = no
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_sasl_mechanism_filter = XOAUTH2

The critical line is smtp_sasl_mechanism_filter = XOAUTH2. Without it, Postfix may try to fall back to PLAIN or LOGIN mechanisms which Exchange Online will reject when basic auth is disabled for your tenant.

After editing main.cf, run:


sudo postmap /etc/postfix/sasl_passwd
sudo postfix reload

The hash: prefix on the password map tells Postfix to use the Berkeley DB version of the file. If your system does not have Berkeley DB support in Postfix, use lmdb: instead:


smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd

Testing the OAuth SMTP connection before switching mail flow

Do not point your live mail flow at the new configuration first. Test it manually.

Generate a test token using the Microsoft Identity Platform token endpoint. On your relay server:


curl -X POST \
  "https://login.microsoftonline.com/your-tenant-id/oauth2/v2.0/token" \
  -d "client_id=your-app-client-id" \
  -d "client_secret=your-client-secret" \
  -d "scope=https://smtp.office365.com/.default" \
  -d "grant_type=client_credentials"

Save the access_token value from the response.

Update the SASL password file with this token:


sudo vi /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd
sudo postfix reload

Now test the SMTP connection manually:


openssl s_client -connect smtp.office365.com:587 -starttls smtp
AUTH XOAUTH2

When prompted for the authentication string, provide it in this format (base64 encoded, all on one line):


base64("user=" + client_id + "^Aauth=Bearer " + access_token + "^A^A")

If the authentication works, you will see a 235 Authentication successful response. If it fails, the error will say "Authentication unsuccessful" rather than a specific OAuth error. The distinction matters: a wrong password produces the same error as a wrong token format.

After confirming AUTH works, send a test message:


MAIL FROM:
RCPT TO:
DATA
Subject: Test message

This is a test.
.
QUIT

Check your Postfix mail log for the delivery result:


sudo tail -20 /var/log/mail.log

What happens when the token expires

Access tokens from Microsoft Entra ID expire after 1 hour by default. After that, the relay will attempt to send and receive "Authentication unsuccessful" from Exchange Online. Mail will queue up and eventually bounce if you do not handle token refresh.

The standard solution is a systemd timer or cron job that runs a script every 50 minutes to refresh the token and update the SASL password file.

Here is a minimal refresh script:

bash
#!/bin/bash
CLIENT_ID="your-app-client-id"
CLIENT_SECRET="your-client-secret"
TENANT_ID="your-tenant-id"
TOKEN_URL="https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token"
PASSWD_FILE="/etc/postfix/sasl_passwd"

TOKEN=$(curl -s -X POST "$TOKEN_URL" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "scope=https://smtp.office365.com/.default" \
-d "grant_type=client_credentials" | jq -r '.access_token')

if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then
echo "Token fetch failed" | logger -t oauth-refresh -p mail.err
exit 1
fi

echo "[smtp.office365.com]:587 $CLIENT_ID:$TOKEN" > "$PASSWD_FILE"
postmap "$PASSWD_FILE"
postfix reload
echo "Token refreshed at $(date)" | logger -t oauth-refresh -p mail.info

Set this script to run every 50 minutes via systemd timer:


/etc/systemd/system/oauth-refresh.timer

[Timer] OnCalendar=:0/50 Persistent=true

[Install]
WantedBy=timers.target

Enable and start it:


sudo systemctl daemon-reload
sudo systemctl enable --now oauth-refresh.timer

If you prefer cron instead:


Run every 50 minutes

/50 /usr/local/bin/refresh-oauth-token.sh

The 10-minute gap between the token lifetime (60 min) and the refresh interval (50 min) gives a comfortable overlap without requiring tight timing.

Validating mail flow after migration

After you switch the relay to OAuth and before you consider the migration done, check three things:

First, confirm mail is leaving the relay and arriving at its destination. Watch the mail log for a few minutes after switching:


sudo tail -f /var/log/mail.log

Look for "status=sent" on your test messages. A "status=deferred" or repeated "connect to smtp.office365.com" messages indicate the OAuth authentication is failing silently.

Second, check that your sending domain's DKIM signing is still working. Exchange Online automatically DKIM-signs messages sent from your tenant, but if your relay changes the envelope-from during transit, alignment may break. If your relay rewrites the Return-Path or envelope-from to an address that is not aligned with your SPF and DKIM selectors, DMARC will fail for messages that would otherwise deliver.

This is also the right moment to check your DMARC reports. If your relay changes the envelope-from address during transit, messages that pass SPF and DKIM can still fail DMARC alignment. That failure will not stop delivery, but it will show up in your aggregate reports as alignment failures, and it means your domain is not getting full protection from DMARC policy enforcement.

With DMARCFlow, you can pull your aggregate reports for the sending domain and look for the Authentication-Results fields on messages sent through the relay. The relevant check is whether the RFC5321.MailFrom domain aligns with the RFC5322.From domain. If you see a sudden increase in alignment failures that started within an hour of your relay migration, the relay is rewriting the envelope-from in a way that breaks DMARC. DMARCFlow shows you which sending IPs are responsible and what the alignment result was, so you can trace it back to the relay configuration change rather than hunting through Postfix logs.

Third, confirm your token refresh script is running correctly. Check the systemd journal or syslog for the oauth-refresh tag:


sudo journalctl -u oauth-refresh.service -f

If the log shows "Token refreshed" every 50 minutes, the refresh is working. If it shows "Token fetch failed," the relay will stop sending within the hour.

Common OAuth SMTP failure modes

"Authentication unsuccessful" without a specific reason. This is the catch-all error when Exchange Online rejects the credentials. It means either the access token is invalid (expired, malformed, or not yet updated after rotation), or the app registration lacks the Mail.Send permission. Token expiry is the most common cause. Confirm your refresh script is running.

AADSTS errors in the token response. If curl returns a JSON body containing "AADSTS" error codes, the app registration has a problem. AADSTS700016 means the application is not found or not configured correctly in the tenant. AADSTS7000112 means the client secret is wrong or expired. AADSTS700016 is common when the redirect URI is missing from the authentication settings.

Certificate chain errors during TLS handshake. If Postfix cannot verify the Microsoft certificate, you will see an error like "SSL routines:tls_process_server_certificate:certificate verify failed" in the mail log. On Debian and Ubuntu, installing the ca-certificates package and running update-ca-certificates resolves this. On RHEL and CentOS, the equivalent is yum install ca-certificates and update-ca-trust.

Token refresh fails because the secret expired. Client secrets in Azure AD have expiration dates. If you set a 1-year expiration when you created the secret, the refresh script will start failing silently after that year. Track secret expiration dates in your calendar or use a certificate instead of a secret, which does not expire.

The relay works but mail goes to spam. If the sending address domain does not have proper SPF, DKIM, and DMARC records, Exchange Online may deliver the message but inbox placement will be poor. This is not an OAuth problem but it often surfaces after a relay migration because the old relay had different envelope-from handling that accidentally passed DMARC.

Frequently asked questions

Do I need a paid Azure AD app registration or does the free tier work?

The free tier of Microsoft Entra ID is sufficient. App registrations are available on all tiers. The only limitation on the free tier is token volume, but for a typical mail relay doing a few hundred messages per hour, the free tier limits are not a practical constraint.

Can I use the same app registration for multiple relay servers?

Yes. The app registration is tied to your tenant, not to individual servers. Multiple Postfix servers can use the same client ID and secret to obtain tokens. However, each server needs its own token refresh process, and each server will have its own token lifetime. This is not a problem as long as each server refreshes independently.

What if my Postfix version does not support XOAUTH2?

Postfix added XOAUTH2 support in version 2.10. If you are running an older version, you need to upgrade. On most Linux distributions, your package manager can do this without much trouble. On RHEL 7 and CentOS 7, the default Postfix version is 2.10 so you are fine. On RHEL 6 and CentOS 6, you will need to upgrade or switch to a third-party SMTP client like msmtp or nullmailer that has OAuth support.

How do I know if my relay is still using basic auth after the deadline?

Two ways to check. First, look at your SASL password file: if it contains a plain password instead of an OAuth access token, you are still on basic auth. Second, connect manually and check what Exchange Online offers:


openssl s_client -connect smtp.office365.com:587 -starttls smtp
EHLO test

Look at the list of authentication mechanisms in the server response. If XOAUTH2 is absent and only PLAIN and LOGIN appear, your tenant has disabled basic auth and OAuth is required. If PLAIN and LOGIN still appear, basic auth may still be active for your tenant, but you should still migrate because Microsoft will disable it eventually.

Does migrating to OAuth affect my DKIM signing?

OAuth does not change whether Exchange Online DKIM-signs your messages. What can change is whether your DMARC alignment passes after migration. Exchange Online DKIM-signs based on your tenant DKIM configuration, not on the authentication method your relay uses. But if your relay rewrites the envelope-from address to something that is not aligned with your RFC5322.From domain, DMARC will fail for messages that would otherwise deliver.

The specific thing to check after migration: look at a DMARC aggregate report for messages sent through the relay and check the alignment result. A DKIM pass with a DMARC fail is the signature of an alignment problem, not a DKIM problem. The fix is usually in the relay's address rewriting rules, not in your DKIM selector configuration.