OAuth 2.0 device flow 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
- OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP
This article shows how to implement OAuth 2.0 device flow to access Office365 via IMAP, POP3 or SMTP using Mail.dll .net email client.
Device flow allows operator/administrator to authenticate your application on a different machine than your application is installed.
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
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";
IPublicClientApplication app = PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.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",
};
Now acquire an access token and a user name:
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 acquire = await app.AcquireTokenWithDeviceCode(
scopes,
callback=>
{
// Write url and code to logs so the operator can react:
Console.WriteLine(callback.VerificationUrl);
Console.WriteLine(callback.UserCode);
// This happens on the first run, manually,
// on the operator machine.
// The code below code is only to illustrate
// the operator opening browser on his machine,
// opening the url and using the code
// (extracted from the application logs)
// to authenticate the app.
System.Diagnostics.Process.Start(
new ProcessStartInfo(callback.VerificationUrl)
{ UseShellExecute = true }
);
return Task.CompletedTask;
}).ExecuteAsync();
userName = acquire.Account.Username;
accessToken = acquire.AccessToken;
}
AcquireTokenWithDeviceCode call waits until operator/administrator gives consent by going to VerificationUrl, entering UserCode and authenticating – this usually happens on a different machine than the application is installed.
Finally your app will exit AcquireTokenWithDeviceCode method and connect using IMAP/POP3/SMTP, authenticate and download emails:
using (Imap client = new Imap())
{
client.ConnectSSL("outlook.office365.com");
client.LoginOAUTH2(userName, accessToken);
client.SelectInbox();
// ...
client.Close();
}
You can find more details on this flow here:
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
Token serialization
Below is a simple implementation that saves MSAL token cache to file. Please note that most likely you should store this cache in an encrypted form:
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);
}
}
}
};
More details on MSAL token serialization are available here:
https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization
Extending Sign-in frequency with policies
You can extend how often operator needs to re-authenticate the application up to 1 year:
Side note: Have in mind that similarly a client credential flow requires a client secret which is valid for 2 years maximum.
Get Mail.dll