- 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
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
- 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 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 uri = await app
.GetAuthorizationRequestUrl(scopes)
.ExecuteAsync();
// Add redirect code to the above
// Microsoft authentication uri and end this request.
}
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();
}
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
The post OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.