iroel
iroel

Reputation: 1798

Unable to auth Microsoft 365 email with OAuth2 for daemon apps

I'm building daemon apps that read the mailbox and converted into ticket issue into our app. This app will connect to M365 mail server through IMAP/POP3 protocol using OAuth2 for authentication. I use MailKit for fetching email. It supports OAuth2 by default. It requires the access token only as password replacement. What've I done

Register the app using this guide. With the following details

  1. I choose to generate client secret instead of using certificate
  2. Authentication is for Accounts in this organizational directory only (single tenant)
  3. Treat application as a public client
  4. API Permissions
    • Exchange: Mail.ReadWrite and granted for admin consent (application permission)
    • Microsoft Graph: User.Read (from default setup)

The rest is unchanged.

using Microsoft.Identity.Client;
using MailKit;
using MailKit.Search;
using MailKit.Security;

const string clientSecret = "xxx";
const string clientId = "yyy";
const string tenant = "zzz";
const string graphApi = "https://graph.microsoft.com/";
const string mailApi = "https://outlook.office.com/";

var apiClient = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithClientSecret(clientSecret)
    .WithAuthority(new System.Uri($"https://login.microsoftonline.com/{tenant}"))
    .Build();
string[] scopes = new[]
{
//    $"{graphApi}.default",

// based on docs: https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
//    $"{mailApi}IMAP.AccessAsUser.All",
//    $"{mailApi}POP.AccessAsUser.All",

// based on URI generated when adding permission
//    "https://outlook.office365.com/Mail.ReadWrite",

// trying to guess
//    $"{graphApi}.Mail.ReadWrite",
//    $"{graphApi}/Mail.ReadWrite",
};
var result = await apiClient.AcquireTokenForClient(scopes).ExecuteAsync();
using (var mailClient = new MailKit.Net.Imap.ImapClient())
{
    await mailClient.ConnectAsync("outlook.office365.com", 993, true);
    var auth = new SaslMechanismOAuth2($"[email protected]", result.AccessToken);
    await mailClient.AuthenticateAsync(auth);
    var mailBox = await mailClient.GetFolderAsync("INBOX");
    await mailBox.OpenAsync(FolderAccess.ReadWrite);
    var ids = await mailBox.SearchAsync(SearchQuery.All);
    foreach (var id in ids)
    {
        var message = await mailBox.GetMessageAsync(id);
        //message.Dump();
    }
}

I've tried for different type of scopes and end up with the following result:

  1. Token acquired when using $"{graphApi}.default" but the mail authentication fails
  2. The rest scope combinations are invalid: AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope xxx is not valid. Trace ID: 210121ba-e915-417a-9220-ade2defd7800 Correlation ID: e13c86c9-bb88-4326-9e38-39f3b7104a92 Timestamp: 2020-08-26 08:00:14Z

Any help will be appreciated. This's copy of question from original MS docs repo.

Upvotes: 1

Views: 1694

Answers (2)

Kevin Z. Li
Kevin Z. Li

Reputation: 566

In order to authenticate IMAP application, you must use https://outlook.office365.com/.default in the scope property in the body payload for the access token request. Please see https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#register-your-application

Upvotes: 0

Jan
Jan

Reputation: 13858

Not 100% sure this is applicable to your scenario but here goes:

We were facing the same issue (thus stumbling onto your post here) and resolved it by using a different flow for requesting the token in the first place. In addition to client_id and client_secret, we provide all the details for a password authentication for the user who's mailbox we want to access:

POST https://login.microsoftonline.com/[your tenantid]/oauth2/v2.0/token

grant_type=password
username=[username for mailbox]
password=[password for that user]
client_id=[application client id]
client_secret=[application_client_secret]
scope=https://outlook.office365.com/IMAP.AccessAsUser.All openid offline_access

(Skipping all the encoding, adding line breaks etc.)

Afterwards, connecting to the IMAP outlook.office365.com with XOAUTH2 for user [username for mailbox] and the token received worked seamlessly.

Upvotes: 1

Related Questions