Office 365: OAuth 2.0 IMAP, POP3 email client connectivity tools

To assist IT managers in resolving Office 365 connectivity issues, Microsoft offers a web-based connectivity analyzer. It works with POP3 or IMAP clients and OAuth 2.0:

Test IMAP access:
https://testconnectivity.microsoft.com/tests/O365Imap/input

Test POP3 access:
https://testconnectivity.microsoft.com/tests/O365Pop/input

Those tools test IMAP and POP3 connectivity only – remember that application in your AD must be properly configured.

You need to choose and implement one of the available OAuth 2.0 flows:
https://www.limilabs.com/blog/office-365-basic-auth-is-disabled

Office 365: Prevent Basic Auth being disabled

Microsoft will be randomly disabling Basic Auth for some tenants before October 1st 2002, then after October 1 2022, they will disable Basic Auth for IMAP and POP3 regardless of their usage.

For some time it was possible to re-enable Basic Auth for IMAP and POP3 for your tenant –
it is no longer possible to do that anymore.

You can find more details here:
https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-deprecation-in-exchange-online-time-s-up/ba-p/3695312

Switch to OAuth 2.0

You should switch to OAuth 2.0 for authentication purposes:

Daemons/Services: Password grant (MFA/2FA must be turned off for this account):
https://www.limilabs.com/blog/oauth2-password-grant-office365-exchange-imap-pop3-smtp

Daemons/Services: Client credential flow:
https://www.limilabs.com/blog/oauth2-client-credential-flow-office365-exchange-imap-pop3-smtp

Web apps (requires user interaction):
https://www.limilabs.com/blog/oauth2-web-flow-office365-exchange-imap-pop3-smtp

Standalone devices (requires very little interaction):
https://www.limilabs.com/blog/oauth2-device-flow-office365-exchange-imap-pop3-smtp

Desktop apps (requires user interaction):
https://www.limilabs.com/blog/oauth2-office365-exchange-imap-pop3-smtp

Below steps are deprecated

If you want to opt-out, read the article below:

https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-and-exchange-online-september-2021-update/ba-p/2772210

…or you can go directly to the self-help diagnostic (use your tenant’s Global Admin account):

https://aka.ms/PillarEXOBasicAuth

It’ll bring up the diagnostic in the Microsoft 365 admin center (if you’re a tenant Global Admin).

Hit Run Tests:

….on the next screen you’ll be able to enable Basic Auth for IMAP or POP3:

For accounts that use shared infrastructure you’ll need to run those 2 powershell commands:

Connect-ExchangeOnline
Enable-OrganizationCustomization

OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP

This article shows how to implement OAuth 2.0 client credential flow to access Office365 via IMAP, POP3 using Mail.dll .net email client. This flow is particularly useful for daemon/service apps that need to monitor certain mailboxes, without any user interaction.

Make sure IMAP/POP3 is enabled for your organization and mailbox:
Enable IMAP/POP3/SMTP in Office 365

Register your application in Azure Portal, here’s a detailed guide how to do that:
https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app

Add permissions to your application in the API permissions / Add a permission wizard:

Select APIs my organization uses and search for Office 365 Exchange Online:

…then click Application permissions:

For POP access, choose the POP.AccessAsApp permission.
For IMAP access, choose the IMAP.AccessAsApp permission.
For SMTP access, choose the SMTP.SendAsApp permission.

Remember to Grant admin consent:

Create an application secret in Certificates & secrets panel by clicking ‘New client secret’ button:

Note the secret value as it is shown only during creation.

Use Windows PowerShell on your machine to Register service principals in Exchange.

Set execution policy first:

Set-ExecutionPolicy RemoteSigned

Install ExchangeOnlineManagement module:

Install-Module -Name ExchangeOnlineManagement 
Import-Module ExchangeOnlineManagement 

Connect and log-in as an administrator (you’ll be prompted for password):

Connect-ExchangeOnline
 -UserPrincipalName your-admin-account@your-domain.onmicrosoft.com

For Exchange running in hybrid mode log-in using following code:

$lc = Get-Credential
Connect-ExchangeOnline -Credential $lc

Create service principal

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

You can find ApplicationId and ObjectId in Enterprise applications in your application’s Overview panel:

Make sure you use the Object ID from the Enterprise Application

Do not use the value from the App Registration screen. 

In our case:

New-ServicePrincipal
 -AppId 061851f7-08c0-40bf-99c1-ebd489c11f16
 -ServiceId 4352fc11-5c2f-4b0b-af40-447ff10664e8

Note: If you still get an error running the New-ServicePrincipal cmdlet after you perform these steps, it is likely due to the fact that the user doesn’t have enough permissions in Exchange online to perform the operation. By default this cmdlet is available to users assigned the Role Management role

Add permissions to a specific mailbox:

Add-MailboxPermission
 -Identity "<USER@your-domain.onmicrosoft.com>"
 -User <OBJECT_ID>
 -AccessRights FullAccess

In our case:

Add-MailboxPermission
 -Identity "AdeleV@your-domain.onmicrosoft.com"
 -User 4352fc11-5c2f-4b0b-af40-447ff10664e8
 -AccessRights FullAccess

Shared mailboxes

You need to use Add-MailboxPermission for every shared mailbox you need access to:

Add-MailboxPermission
 -Identity "shared@your-domain.onmicrosoft.com"
 -User <OBJECT_ID>
 -AccessRights FullAccess

Let’s code

Use Microsoft Authentication Library for .NET (MSAL.NET) nuget package to obtain an access token:
https://www.nuget.org/packages/Microsoft.Identity.Client/

// C#

string clientId = "Application (client) ID";    // 061851f7-...
string tenantId = "Directory (tenant) ID";
string clientSecret = "Client secret value";

string userName = "Username/email for mailbox";    // AdeleV@...

var app = ConfidentialClientApplicationBuilder
    .Create(clientId)
    .WithTenantId(tenantId)
    .WithClientSecret(clientSecret)
    .Build();

string[] scopes = new string[] { 
    "https://outlook.office365.com/.default" 
};
' VB.NET

Dim clientId As String = "Application (client) ID" ' 061851f7-...
Dim tenantId As String = "Directory (tenant) ID"
Dim clientSecret As String = "Client secret value"

Dim userName As String = "Username/email for mailbox"  'AdeleV@...

Dim app = ConfidentialClientApplicationBuilder.Create(clientId) _
    .WithTenantId(tenantId) _
    .WithClientSecret(clientSecret) _
    .Build()

Dim scopes As String() = New String() { _
    "https://outlook.office365.com/.default" _
}

Now acquire an access token:

// C#

var result = await app.AcquireTokenForClient(scopes)
    .ExecuteAsync();

string accessToken = result.AccessToken;
' VB.NET

Dim result = Await app.AcquireTokenForClient(scopes).ExecuteAsync()
Dim accessToken As String = result.AccessToken

Finally you can connect using IMAP/POP3, authenticate and download user’s emails:

// C#

using (Imap client = new Imap())
{
    client.ConnectSSL("outlook.office365.com");
    client.LoginOAUTH2(userName, accessToken);
 
    client.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);
    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
                .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
   }

   client.Close();
} 
' VB.NET

Using client As Imap = New Imap()
    client.ConnectSSL("outlook.office365.com")
    client.LoginOAUTH2(userName, accessToken)

    client.SelectInbox()

    Dim uids As List(Of Long) = imap.Search(Flag.Unseen)
    For Each uid As Long In uids
        Dim email As IMail = New MailBuilder() _
            .CreateFromEml(imap.GetMessageByUID(uid))
        Dim subject As String = email.Subject
    Next

    client.Close()
End Using

SMTP

Microsoft started supporting client credential flow and SMTP recently.

SMTP requires SMTP.SendAsApp permission added to your AD application.

All other OAuth flows (webdesktoppassword grantdevice) support SMTP client access as well.

For SMTP non-OAuth2 access:

SMTP AUTH will still be available when Basic authentication is permanently disabled on October 1, 2022.” (https://docs.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online)

However Microsoft disables SMTP AUTH in all tenants in which it’s not being used.

Here’s how to enable SMTP AUTH:
https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission

Troubleshooting

1. Start with PowerShell commands:

Get-ServicePrincipal
Get-MailboxPermission -Identity "AdeleV@your-domain.onmicrosoft.com"

You should see following results:

Make sure the ServiceId is the same as the Object ID on the Enterprise Application screen (do not use the value from the App Registration screen)

Make sure the AppId is the same as the Application ID on the Enterprise Application screen

2. Check if you can connect to this account using IMAP and regular interactive flow:

https://www.limilabs.com/blog/office-365-oauth-2-0-imap-pop3-email-client-connectivity-tools

This proves you have IMAP access properly configured.

3. Check if you added correct permissions and have granted Admin consent for your domain.

4. Usually people use incorrect client/tenant ids/secrets – double check every single value you enter (also for additional spaces).

5. You may need to wait 20-30 minutes for some changes to take effect (it really may take this long!).

Additional links

https://docs.microsoft.com/en-us/powershell/exchange/exchange-online-powershell-v2?view=exchange-ps#install-and-maintain-the-exo-v2-module
https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#use-client-credentials-grant-flow-to-authenticate-imap-and-pop-connections


Get Mail.dll

OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP

This article shows how to implement OAuth 2.0 web flow to access Office365 via IMAP, POP3 or SMTP using Mail.dll .net email client.

Make sure IMAP/POP3/SMTP is enabled for your organization and mailbox:
Enable IMAP/POP3/SMTP in Office 365

Register your application in Azure Portal, here’s a detailed guide how to do that:
https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app

RedirectUri

Add an authentication redirect uri to your application:

Then you need to apply correct API permissions and grant the admin consent for your domain.

In the API permissions / Add a permission wizard, select Microsoft Graph and then Delegated permissions to find the following permission scopes listed:

  • offline_access
  • email
  • IMAP.AccessAsUser.All
  • POP.AccessAsUser.All
  • SMTP.Send

Remember to Grant admin consent:

Create an app secret and remember its value:

Use Microsoft Authentication Library for .NET (MSAL.NET) nuget package to obtain an access token:
https://www.nuget.org/packages/Microsoft.Identity.Client/

string clientId = "Application (client) ID";
string tenantId = "Directory (tenant) ID";
string clientSecret = "Client secret value";

// for @outlook.com/@hotmail accounts instead of setting .WithTenantId use:
// .WithAuthority(AadAuthorityAudience.PersonalMicrosoftAccount)

var app = ConfidentialClientApplicationBuilder
    .Create(clientId)
    .WithTenantId(tenantId)
    .WithClientSecret(clientSecret)
    .WithRedirectUri("http://localhost/myapp/")
    .Build();
// This allows saving access/refresh tokens to some storage
TokenCacheHelper.EnableSerialization(app.UserTokenCache);

var scopes = new string[] 
{
    "offline_access",
    "email",
    "https://outlook.office.com/IMAP.AccessAsUser.All",
    "https://outlook.office.com/POP.AccessAsUser.All",
    "https://outlook.office.com/SMTP.Send",
};

In addition, you should request offline_access scope. When a user approves the offline_access scope, your app can receive refresh tokens from the Microsoft identity platform token endpoint. Refresh tokens are long-lived. Your app can get new access tokens as older ones expire.

Now try finding account by an identifier (it will be null on first access) in MSAL cache:

string userName;
string accessToken;

string identifier = null;

var account = await app.GetAccountAsync(identifier);

try
{
    AuthenticationResult refresh = await app
        .AcquireTokenSilent(scopes, account)
        .WithForceRefresh(true)
        .ExecuteAsync();

    userName = refresh.Account.Username;
    accessToken = refresh.AccessToken;

}
catch (MsalUiRequiredException e)
{
    // no token cache entry - perform authentication:

    Uri msUri = await app
        .GetAuthorizationRequestUrl(scopes)
        .ExecuteAsync();

    // Add a redirect code to the above 
    // Microsoft authentication uri and end this request.
}

On the first run user will be redirected to the msUri and will see a Microsoft login screen, with option to log-in, using a known account and granting access to the app (if needed):

After successful authentication Microsoft will redirect user’s browser back to your application – to the app’s RedirectUri (in our case http://localhost/MyApp/):

http://localhost/myapp/?code=0.Aa…AA&client_info=ey…I0In0&session_state=4dd….4488c8#

Controller responsible for handling this request should retrieve code parameter

string code = "get from url after redirect";

AuthenticationResult result = await app
    .AcquireTokenByAuthorizationCode(scopes, code)
    .ExecuteAsync();

string identifier = result.Account.HomeAccountId.Identifier;
string userName = result.Account.Username;
string accessToken = result.AccessToken;

Finally you can connect using IMAP/POP3/SMTP, authenticate and download user’s emails:

using (Imap client = new Imap())
{
    client.ConnectSSL("outlook.office365.com");
    client.LoginOAUTH2(userName, accessToken);
 
    client.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);
    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
                .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
   }

    client.Close();
} 

Any organization and personal accounts

To access accounts from any organization and personal accounts as well, you need to specify correct account types when you create the App in your AD:

Additionally you need to use:    

    .WithAuthority(
        AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
        )

instead of

    .WithTenantId(tenantId)

when creating the app:

var app = ConfidentialClientApplicationBuilder
    .Create(clientId)
    .WithAuthority(
        AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
        )
    .WithClientSecret(clientSecret)
    .WithRedirectUri("http://localhost/myapp/")
    .Build();

Token serialization

Below is a simple implementation that saves MSAL token cache to file:

static class TokenCacheHelper
{
    public static void EnableSerialization(ITokenCache tokenCache)
    {
        tokenCache.SetBeforeAccess(BeforeAccessNotification);
        tokenCache.SetAfterAccess(AfterAccessNotification);
    }

    private static readonly string _fileName = "msalcache.bin3";

    private static readonly object _fileLock = new object();


    private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
    {
        lock (_fileLock)
        {
            byte[] data = null;
            if (File.Exists(_fileName))
                data = File.ReadAllBytes(_fileName);
            args.TokenCache.DeserializeMsalV3(data);
        }
    }

    private static void AfterAccessNotification(TokenCacheNotificationArgs args)
    {
        if (args.HasStateChanged)
        {
            lock (_fileLock)
            {
                byte[] data = args.TokenCache.SerializeMsalV3();
                File.WriteAllBytes(_fileName, data);
            }
        }
    }
};

Please note that most likely you should store this cache in an encrypted form in some kind of a database.
Consider using MSAL token serialization implementations available here:

https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization


Get Mail.dll

Office365: Client Credential Flow support for POP/IMAP is coming in June 2022

[Update]

Client credential flow is now supported:

https://www.limilabs.com/blog/oauth2-client-credential-flow-office365-exchange-imap-pop3-smtp


Microsoft is working on bringing OAuth Client Credential Flow support for POP/IMAP for Office365/Exchange. It’s planned to be released in June 2022:

https://www.microsoft.com/en-us/microsoft-365/roadmap?filters=&searchterms=70577

Check out currently supported OAuth 2.0 flows