jstedfast / MailKit

A cross-platform .NET library for IMAP, POP3, and SMTP.

Home Page:http://www.mimekit.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Office365 and ConfidentialClientApplicationBuilder

tskong opened this issue · comments

Hi,

I know this has been covered extensively in Issue 1126, but has anyone got this working? The issue was closed without any one confirming if the solution from microsoft worked. I've tried it and it doesn't work for me.

The the ExchangeOAuth2.md)as incorrect as the AcquireTokenInteractive doesn't exist for ConfidentialClientApplicationBuilder.

I get MailKit.Security.AuthenticationException: '535: 5.7.3 Authentication unsuccessful when I try to authenticate, but the PublicClientApplicationBuilder is fine.

As the majority of our customers are office365, this is something I need to get working, unless I move to graph.

Thanks

Sorry, that should have been AcquireClientToken(scopes), I think. I've updated the code snippet in the doc.

I think its AcquireTokenForClient(), AcquireClientToken doesn't exist().

Thanks

Sorry, you are right - thank you. I've updated the docs.

A more accurate code snippet might be:

var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create (clientId)
    .WithCertificate (certificate) // or .WithClientSecret (clientSecret)
    .Build ();
 
var scopes = new string[] {
    // For IMAP and POP3, use the following scope
    "https://ps.outlook.com/.default"

    // For SMTP, use the following scope
    // "https://outlook.office365.com/.default"
};

var authToken = await confidentialClientApplication.AcquireTokenForClient (scopes)
    .WithAuthority (AzureCloudInstance.AzurePublic, "{tenantID}").ExecuteAsync ();
var oauth2 = new SaslMechanismOAuth2 (authToken.Account.Username, authToken.AccessToken);

using (var client = new ImapClient ()) {
    await client.ConnectAsync ("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
    await client.AuthenticateAsync (oauth2);
    await client.DisconnectAsync (true);
}

The problem with ConfidentialClientApplication auth flow is there is so many options and different ways of registering your app and all that, that it's impossible to post docs on my end that are 100% guaranteed to work for your specific config.

This whole thing has been a massive headache for me due to all of the frustration.

Agreed, it's a headache for everyone.

I'm still having problems, I'm suspicious that Microsoft have not fixed SMTP OAUTH for server side applications. I will persevere

I've got it working now. Turns out someone else had created an app with exactly the same name as the one I was using, so I had registered the wrong appid/objectid with exchange.

Here's my (working) sample, hopefully it will be of use to someone

 static async Task ConfidentialClient()
        {
            const string appid = "<your app id>";
            const string tenant = "<your tenant id>";
            const string appSecret = "<secret for appid>;

            var app = ConfidentialClientApplicationBuilder.Create(appid)
                                    .WithAuthority($"https://login.microsoftonline.com/{tenant}/v2.0")
                                    .WithClientSecret(appSecret)
                                    .Build();
            var scopes = new string[] {
                "https://outlook.office365.com/.default"
             };

            AuthenticationResult authToken = await app.AcquireTokenForClient(scopes)
                           .ExecuteAsync();

            var oauth2 = new SaslMechanismOAuth2("<I don't know what this is for, I used the from email address>", authToken.AccessToken);

            var mailmessage = new MimeMessage();

            mailmessage.From.Add(new MailboxAddress("<from>", "<from email>"));
            mailmessage.To.Add(new MailboxAddress("<to>", "<to email>"));
            mailmessage.Subject = "From mailkit";
            mailmessage.Body = new TextPart()
            {
                Text = "This is a test " + DateTime.Now.ToString()
            };

            using (var client = new SmtpClient())
            {
                client.Connect("outlook.office365.com");
                client.Authenticate(oauth2);
                client.Send(mailmessage);
            }

        }

Then in powershell

[System.Net.ServicePointManager]::SecurityProtocol = 'TLS12'
Connect-ExchangeOnline -Organization <your tenant>

Now, look for your application in "enterprise applications" (not applications, this is where I went wrong) in azure, the appid will be the same, but the objectid will differ

New-ServicePrincipal -AppId <your app id> -ObjectId <enterprise object id>

Then check if it's there, and make a note of the identity field
Get-ServicePrincipal

Add-MailboxPermission -Identity "<mailbox used to send>" -User <identity from getserviceprincipal> -AccessRights FullAccess

I have a Web App following the directions https://github.com/jstedfast/MailKit/blob/master/ExchangeOAuth2.md
I dont have access to Exchange Online PowerShell but I think the App is setup correctly
trying to use the code

    var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(clientId)
        .WithClientSecret(appSecret)
        .Build();

    var scopes = new string[] {
        // For IMAP and POP3, use the following scope
        "https://ps.outlook.com/.default"

        // For SMTP, use the following scope
        // "https://outlook.office365.com/.default"
    };

    var authToken = await confidentialClientApplication.AcquireTokenForClient(scopes)
        .WithAuthority(AzureCloudInstance.AzurePublic, tenantId).ExecuteAsync();

    var oauth2 = new SaslMechanismOAuth2("myemail@domain.net.au", authToken.AccessToken);

    await client.ConnectAsync("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
    await client.AuthenticateAsync(oauth2);

I get an authToken but the authToken.Account.Username is null so using the Email Username

await client.AuthenticateAsync(oauth2); returns the error
ex.Message = "Authentication failed."

Also tried the ConfidentialClientApplicationBuilder above with the same result

@SelectSystemsInternational Use the code snippet that worked for @tskong

I tried that and it gives the same result

The authToken.Account.Username will be null as you are authenticating a app not a client, the email address is fine.

You MUST run the powershell scripts, it registers the app with exchange, if you don't you'll get the error Authentication failed. As an experiment, I got it working, and then reversed the powershell changes and got the authentication failure errors.

Sorry how do you run the Powershell Scripts ?
I can do

  • Install-Module -Name ExchangeOnlineManagement
  • Import-module ExchangeOnlineManagement
  • Connect-ExchangeOnline -Organization

But when I run Powershell (In administrator mode) from the desktop it says the commands

  • New-ServicePrincipal and Get-ServicePrincipal, etc do not exist

Do you need an Azure service or ?

I have no idea, you'll have to read the documentation.

Those cmdlets are part of the ExchangeOnlineManagement module, so you should have them. What's the full error? It might give us some clues.

I needed some extra commands for PowerShell to use those commands

Install-Module -Name Az.StackHCI
Connect-AzAccount (then login to Azure via popup)

Then the rest as per documentation

Install-Module -Name ExchangeOnlineManagement -allowprerelease
Import-module ExchangeOnlineManagement
Connect-ExchangeOnline -Organization
New-ServicePrincipal -AppId <APPLICATION_ID> -ObjectId <OBJECT_ID>
Get-ServicePrincipal | fl
Add-MailboxPermission -Identity "john.smith@contoso.com" -User <SERVICE_PRINCIPAL_ID> -AccessRights FullAccess

Thankfully I got the client to work now, but one issue was the https://ps.outlook.com/.default scope did not work and caused Authentication failed. error - so now using the outlook scope for both SMTP and IMAP with the final code as per the documentation

var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(appId)
            .WithAuthority($"https://login.microsoftonline.com/{tenantId}/v2.0")
            .WithClientSecret(clientSecret)
            .Build();

var scopes = new string[] {
    "https://outlook.office365.com/.default"
};

var authToken = await confidentialClientApplication.AcquireTokenForClient(scopes).ExecuteAsync();
var oauth2 = new SaslMechanismOAuth2(accountEmailAddress, authToken.AccessToken);

client.Connect("outlook.office365.com");
client.Authenticate(oauth2);

The other PowerShell command needed to enable SMTP was

Set-TransportConfig -SmtpClientAuthenticationDisabled $true
and
Get-TransportConfig to check the change