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:
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 thatanymore.
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.
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.
Note: If you still get an error running the New-ServicePrincipal cmdlet after you perform these steps, it is likely due to the fact thatthe 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
' 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.
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:
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/):
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: