OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP
- OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP
- OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP
- OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP
- OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP
This article shows how to implement OAuth 2.0 desktop 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
Remember to add authentication entries (localhost is needed for .net core):

RedirectUri
.NET desktop: https://login.microsoftonline.com/common/oauth2/nativeclient
.NET core/.NET 5,6,7+: http://localhost
ASP.NET: your application custom url
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
- IMAP.AccessAsUser.All
- POP.AccessAsUser.All
- SMTP.Send
Remember to Grant admin consent..
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";
// for @outlook.com/@hotmail accounts instead of setting .WithTenantId use:
// .WithAuthority(AadAuthorityAudience.PersonalMicrosoftAccount)
var app = PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithDefaultRedirectUri()
.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 acquire the access token and user email address:
string userName;
string accessToken;
var account = (await app.GetAccountsAsync()).FirstOrDefault();
try
{
AuthenticationResult refresh = await app
.AcquireTokenSilent(scopes, account)
.ExecuteAsync();
userName = refresh.Account.Username;
accessToken = refresh.AccessToken;
}
catch (MsalUiRequiredException e)
{
var result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
userName = result.Account.Username;
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();
}
As this is fairly new feature for Exchange/Office365, here are some useful links:
https://stackoverflow.com/questions/29747477/imap-auth-in-office-365-using-oauth2
https://stackoverflow.com/questions/43473858/connect-to-outlook-office-365-imap-using-oauth2
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