Using MailKit to connect to o365 with oAuth from WPF app

68 views Asked by At

Just started a new WPF project that needs to connect to 365 for SMPT and IMAP.

I have obtained the following from Entra ClientID TenantID SecretValue SecretID

But I'm struggling to put it all together, so started with basic authentication first...

Public Function SendMail(MailBody As String, MailSubject As String, SendTo As String, SentFrom As String) As String
Try
    Dim vEmail = New MimeMessage()
    vEmail.From.Add(MailboxAddress.Parse(SentFrom))
    vEmail.To.Add(MailboxAddress.Parse(SendTo))
    vEmail.Subject = MailSubject
    vEmail.Body = New TextPart(TextFormat.Html) With {.Text = MailBody}
    Using vClient = New SmtpClient
        vClient.Connect("smtp-legacy.office365.com", 587, SecureSocketOptions.StartTls)
        vClient.Authenticate(MailUserName, MailPasssword)
        vClient.Send(vEmail)
        vClient.Disconnect(True)
    End Using
    Return "Email was sent!"
Catch ex As Exception
    EmailError(ex)
    Return "Email sending failed"
End Try
End Function

and that works.

Tried this based on an example at the MailKit site...

Public Function SendAuthMail(MailBody As String, Mailsubject As String, SendTo As String, SentFrom As String) As String
Try
    Dim vEmail = New MimeMessage()
    vEmail.From.Add(MailboxAddress.Parse(SentFrom))
    vEmail.To.Add(MailboxAddress.Parse(SendTo))
    vEmail.Subject = Mailsubject
    vEmail.Body = New TextPart(TextFormat.Html) With {.Text = MailBody}
    Dim options = New PublicClientApplicationOptions With {.ClientId = vClientID,
    .TenantId = vTenantID,
    .RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"}
    Dim scopes = New String() {"email", "offline_access", "https://outlook.office.com/SMTP.Send"}
    Dim publicClientApplication = PublicClientApplicationBuilder.CreateWithApplicationOptions(options).Build()
    Dim authToken = publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync()
    Dim v2 = New SaslMechanismOAuth2(authToken.Account.Username, authToken.AccessToken)

    Using vClient = New SmtpClient
        vClient.Connect("smtp.office365.com", 587, SecureSocketOptions.StartTls)
        vClient.Authenticate(v2)
        vClient.Send(vEmail)
        vClient.Disconnect(True)
    End Using
    Return "Email was sent"
Catch ex As Exception
    EmailError(ex)
    Return "Email sending failed"
End Try
End Function

But authToken.Account.Username and authtoken.AccessToken both fail with 'Account' is not a member of 'Task(Of AuthenticationResult)' and 'AccessToken' is not a member of 'Task(Of AuthenticationResult)

Any pointers would be appreciated - the desk is proving to be much harder than my head!

============= EDIT ===========

Using...

  Dim AuthClient = ConfidentialClientApplicationBuilder.Create(vClientID).
                                     WithAuthority(AzureCloudInstance.AzurePublic, vTenantID).
                                     WithClientSecret(SecretValue).
                                     Build

Dim AuthResult = AuthClient.AcquireTokenForClient({"https://outlook.office365.com/.default"}).ExecuteAsync.Result Dim vAuth As New SaslMechanismOAuth2(SentFrom, AuthResult.AccessToken)

I can now get to 'authentication failed' - but it doesn't really give me a clue as to why.

==================Further edit=================

The main problem seems to be authentication - and it would appear to to be down to SaslMechanismOuth2 and Authtoken

It should (according to all the examples I have seen) expose .Account.UserName and .AccessToken - but they are not available.

SaslMechanismOuth2

If you look through the connection dialogue here, you can see that the server just drops the connection with starttls, but connects with ssl. However, you can see that AUTHENTICATE XOAUTH2 fails to authenticate. There should be a long string.

Connected to imap://outlook.office365.com:993/?starttls=always
Connected to imap://outlook.office365.com:993/?starttls=always
Connected to imap://outlook.office365.com:993/?starttls=always
Connected to imaps://outlook.office365.com:993/
S: * OK The Microsoft Exchange IMAP4 service is ready. [TABPADQAUAAxADIAMwBDAEEAMAAxADAAMgAuAEcAQgBSAFAAMQAyADMALgBQAFIATwBEAC4ATwBVAFQATABPAE8ASwAuAEMATwBNAA==]
C: A00000000 CAPABILITY
S: * CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
S: A00000000 OK CAPABILITY completed.
C: A00000001 AUTHENTICATE XOAUTH2 ********
S: A00000001 NO AUTHENTICATE failed.
2

There are 2 answers

0
gchq On BEST ANSWER

It's now working - and there were a couple of factors if you rely on the MailKit documentation here

Firstly scopes - this should be just

Dim scopes = New String() {"https://outlook.office365.com/.default"}

Secondly, you must use result for authToken and add the email user name as text...

Dim authToken = vAppBuilder.AcquireTokenForClient(scopes).ExecuteAsync()
vResult = authToken.Result
vToken = vResult.AccessToken
Dim vText = authToken.result.ToString
Dim vT2 = New SaslMechanismOAuth2(MailUserName, vToken)

Putting the entire thing together

Private Async Sub ReturnIMAP()
Dim vImage As New LoadingImage
Try

    LoadingStarted("Running IMAP... Please wait...", vImage)
    Dim vT2Text As String = ""
    Dim vResult = Nothing
    Dim vToken = Nothing
    Dim vNumber As Integer = 0
    ' Dim vT3 = Nothing
    '  Dim vT4 As String = ""
    Dim vMessage As String = ""
    Await Task.Run(Sub()
                       Dim vAppBuilder = ConfidentialClientApplicationBuilder.Create(vClientID).WithClientSecret(SecretValue).WithTenantId(vTenantID).WithAuthority(RedirectUri).Build()
                       Dim scopes = New String() {"https://outlook.office365.com/.default"}

                       Dim authToken = vAppBuilder.AcquireTokenForClient(scopes).ExecuteAsync()
                       vResult = authToken.Result
                       vToken = vResult.AccessToken
                       Dim vText = authToken.result.ToString
                       Dim vT2 = New SaslMechanismOAuth2(MailUserName, vToken)


                       Using vClient As New ImapClient(New ProtocolLogger("C:\Temp\imap3.log"))
                           vClient.Connect("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect)
                           vClient.Authenticate(vT2)
                           vClient.Inbox.Open(FolderAccess.ReadWrite)
                           vNumber = vClient.Inbox.Count
                           If vNumber > 0 Then

                               Using wr As New StreamWriter("C:\Temp\EmailMessages.txt", True)
                                   vClient.Inbox.Open(FolderAccess.ReadOnly)
                                   Dim UIDS = vClient.Inbox.Search(SearchQuery.All)
                                   For i As Integer = 0 To UIDS.Count - 1
                                       Dim Message = vClient.Inbox.GetMessage(i)

                                       Message.WriteTo(String.Format("{0}.eml", i))
                                       vMessage = Message.ToString
                                       wr.WriteLine(vMessage)


                                   Next

                               End Using
                           End If
                           vClient.Disconnect(True)
                       End Using



                   End Sub)

    LoadingCompleted("Ready", "Inbox unread messages = " & vNumber, vImage)

Catch ex As Exception
    EmailError(ex)
    LoadingCompleted("Error", "Error", vImage)
End Try
End Sub  
3
mm8 On

You should await the Task(Of T) returned by the ExecuteAsync method:

Public Async Function SendAuthMail(MailBody As String, Mailsubject As String, SendTo As String, SentFrom As String) As Task(Of String)
    Try
        ...
            Dim authToken = Await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync()
        ...
            Return "Email was sent"
    Catch ex As Exception
        EmailError(ex)
        Return "Email sending failed"
    End Try
End Function