wpfmailkit

Using MailKit to connect to o365 with oAuth from WPF app


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.

Solution

  • 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