Search code examples
c#smtpgoogle-oauthgoogle-api-dotnet-clientmailkit

MailKit and OAuth2 for gmail SMTP - 5.7.8 BadCredentials error


I'm trying to use mailkit to send emails via a gmail account. I keep getting the 5.7.8 BadCredentials error.

My Oauth2 credentials in google specify a project name of "EmailTesting" for a desktop app - however I'm not specifying this string anywhere in my code. Is this a problem? The credentials.json file specified in the code is directly downloaded from the oauth2 credentials page.

UPDATE: Now using SaslMechanismOAuthBearer method since GMAIL indicates OAuthBearer. Also showing smtp.log at end.

My code is as follows:

    public async Task Test4()
    {
        UserCredential creds;
        using (var stream = new FileStream("credentials.json", FileMode.Open, FileAccess.Read))
        {
            creds = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.FromStream(stream).Secrets,
                new[] { GmailService.Scope.GmailSend },
                "donotreply@INSERT_DOMAIN_HERE",
                CancellationToken.None);
            if (creds.Token.IsExpired(SystemClock.Default))
            {
                await creds.RefreshTokenAsync(CancellationToken.None);
            }
        }

        MailKit.Net.Smtp.SmtpClient client;
        client = new MailKit.Net.Smtp.SmtpClient();
        client.Connect("smtp.gmail.com", 465, MailKit.Security.SecureSocketOptions.SslOnConnect);
        client.Authenticate(new SaslMechanismOAuthBearer(creds.UserId, creds.Token.AccessToken));
        var builder = new MimeKit.BodyBuilder();

        builder.TextBody = "This is the body of the email";
        MimeMessage mail = new MimeKit.MimeMessage();
        mail.From.Add(MailboxAddress.Parse("donotreply@INSERT_DOMAIN_HERE"));
        mail.To.Add(MailboxAddress.Parse("erict@INSERT_DOMAIN_HERE"));
        mail.Subject = "this is the subject";
        mail.Body = builder.ToMessageBody();
        await client.SendAsync(mail);
        client.Disconnect(true);
        client.Dispose();
        mail.Dispose();
    }

The first day I ran this, it redirected to a web page so that donotreply@INSERT_DOMAIN_HERE could give permission for the app to send emails. However, after that and since then, it would throw a 5.7.8 Username and Password not accepted error. All day yesterday the google 5.7.8 learn more link suggested I need to add a recovery phone - even though I had done that more than 24h ago, and verified. As of this morning, google does not add that suggestion anymore, but still throws a 5.7.8 error. Here is a log output:

S: 220 smtp.gmail.com ESMTP r12-20020a05621410cc00b0066d132b1c8bsm4912733qvs.102 - gsmtp
C: EHLO New-Laptop
S: 250-smtp.gmail.com at your service, [<IP-ADDRESS OF MY MACHINE>]
S: 250-SIZE 35882577
S: 250-8BITMIME
S: 250-STARTTLS
S: 250-ENHANCEDSTATUSCODES
S: 250-PIPELINING
S: 250-CHUNKING
S: 250 SMTPUTF8
C: STARTTLS
S: 220 2.0.0 Ready to start TLS
C: EHLO New-Laptop
S: 250-smtp.gmail.com at your service, [<IP-ADDRESS OF MY MACHINE>]
S: 250-SIZE 35882577
S: 250-8BITMIME
S: 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
S: 250-ENHANCEDSTATUSCODES
S: 250-PIPELINING
S: 250-CHUNKING
S: 250 SMTPUTF8
C: AUTH OAUTHBEARER ********
S: 334 eyJzdGF0dXMiOiJpbnZhbGlkX3JlcXVlc3QiLCJzY29wZSI6Imh0dHBzOi8vbWFpbC5nb29nbGUuY29tLyJ9
C: ********
S: 535-5.7.8 Username and Password not accepted. Learn more at...

Solution

  • First you need to use the "https://mail.google.com/" to use the SMTP server second AuthenticateAsync should be using SaslMechanismOAuth2

    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Util.Store;
    using MailKit.Net.Smtp;
    using MailKit.Security;
    using MimeKit;
    
    var to = "[email protected]";
    
    // TODO: figure out how to get the users email back from the smtp server without having to request the profile scope. 
    var from = "[email protected]";
    
    var path = @"C:\Development\FreeLance\GoogleSamples\Credentials\Credentials.json";
    var scopes = new[] { "https://mail.google.com/" };   // You can only use this scope to connect to the smtp server.
    
    
    
    var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromFile(path).Secrets,
        scopes,
        "GmailSmtpUser",
        CancellationToken.None,
        new FileDataStore(Directory.GetCurrentDirectory(), true)).Result;
    
    var message = new EmailMessage()
    {
        From = from,
        To = to,
        MessageText = "This is a test message using https://developers.google.com/gmail/imap/xoauth2-protocol",
        Subject = "Testing GmailSMTP with XOauth2"
    };
    
    try
    {
        using (var client = new SmtpClient())
        {
            client.Connect("smtp.gmail.com", 465, true);
            
            var oauth2 = new SaslMechanismOAuth2 (message.From, credential.Token.AccessToken);
            await client.AuthenticateAsync (oauth2, CancellationToken.None);
            
            client.Send(message.GetMessage());
            client.Disconnect(true);
        }
    
       
    }
    catch (Exception ex)
    {
       Console.WriteLine(ex.Message);
    }
    
    
    public class EmailMessage
    {
        public string To { get; set; }
        public string From { get; set; }
        public string Subject { get; set; }
        public string MessageText { get; set; }
    
        public MimeMessage GetMessage()
        {
            var body = MessageText;
            var message = new MimeMessage();
            message.From.Add(new MailboxAddress("From a user", From));
            message.To.Add(new MailboxAddress("To a user", To));
            message.Subject = Subject;
            message.Body = new TextPart("plain") { Text = body };
            return message;
        }
    }