添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
开朗的茄子  ·  tableau API如何访问 | ...·  7 小时前    · 
绅士的熊猫  ·  获取Token - NG-ALAIN·  1 周前    · 
坏坏的板栗  ·  Python ...·  3 月前    · 
淡定的炒饭  ·  百度贴吧·  4 月前    · 

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement . We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I'm trying to support "secure" OAUTH2.0 access to gmail so that our users don't need to "allow less secure apps". I set up my credentials and such and I'm able to use the Gmail API to authenticate and send emails. However, I'd prefer to use MailKit rather than the Gmail API (for several reasons beyond the scope of this question).

I'm attempting to use the Gmail API to do the OAUTH2.0 authentication and retrieve an access token, then use that token to authenticate a MailKit SMTP/IMAP client so that I can send/retrieve/manage emails using MailKit rather than the Gmail API. I was hoping I could just pass the access token as the 'password' for the authentication (from this thread ), but it seems to treat that as a user password and it fails:

MailKit.Security.AuthenticationException occurred
HResult=0x80131500
Message=AuthenticationInvalidCredentials: 5.7.8 Username and Password not accepted. Learn more at
5.7.8 https://support.google.com/mail/?p=BadCredentials b84sm4142328ljf.60 - gsmtp
Source=
StackTrace:
at MailKit.Net.Smtp.SmtpClient.Authenticate(Encoding encoding, ICredentials credentials, CancellationToken cancellationToken)
at MailKit.MailService.<>c__DisplayClass51_0.b__0()
at System.Threading.Tasks.Task.Execute()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at AccessibleEmail.EmailClient.MailKit.Authenticator.d__5.MoveNext()

I tried removing all authentication mechanisms except XOAUTH2 to try and force it to treat it as an access token, but this failed with a different error:

MailKit.Security.AuthenticationException occurred
HResult=0x80131500
Message=AuthenticationChallenge: <Some base64-looking string that might be my access token?>
Source=
StackTrace:
at MailKit.Net.Smtp.SmtpClient.Authenticate(Encoding encoding, ICredentials credentials, CancellationToken cancellationToken)
at MailKit.MailService.<>c__DisplayClass51_0.b__0()
at System.Threading.Tasks.Task.Execute()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at AccessibleEmail.EmailClient.MailKit.Authenticator.d__5.MoveNext()

I have the protocol log files available but they contain the access key so I will email them.

Here's some sample code (I didn't actually run this):

    public class GmailExample
        public async Task AuthenticateClientDemo()
            using (var client = new SmtpClient())
                client.Connect("smtp.google.com", 465, true);
                //Obviously fake data for the purpose of the demo
                var secrets = new ClientSecrets
                    ClientId = "foo.apps.googleusercontent.com",
                    ClientSecret = "foosecret"
                var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    secrets,
                    new[] { GmailService.Scope.MailGoogleCom }, "[email protected]", CancellationToken.None);
                    AuthenticationAttempt1(client, credential);
                catch (Exception)
                    AuthenticationAttempt2(client, credential);
                catch (Exception)
        private void AuthenticationAttempt1(SmtpClient client, UserCredential oauthCredentials)
            //Now that I have the access token, can I use it to authenticate the smtp client?
            var networkCredentials = new NetworkCredential("[email protected]", oauthCredentials.Token.AccessToken); //This doesn't seem right because it's interpreting the access token as a user password
            client.Authenticate(networkCredentials); //This throws an invalid password exception
        private void AuthenticationAttempt2(SmtpClient client, UserCredential oauthCredentials)
            //Remove all other authentication mechanisms to force it to use OAUTH2 and hopefully interpret the access token as such rather than a user password
            client.AuthenticationMechanisms.Clear();
            client.AuthenticationMechanisms.Add("XOAUTH2");
            var networkCredentials = new NetworkCredential("[email protected]", oauthCredentials.Token.AccessToken);
            client.Authenticate(networkCredentials); //Now this throws an AuthenticationException "AuthenticationChallenge'

So basically I'm wondering if this is even possible, and if so, how do I do it?

P.S. This is basically my question except that the answer you posted isn't working for me.
https://stackoverflow.com/a/24204968/6391640

AH! GoogleWebAuthorizationBroker.AuthorizeAsyn doesn't actually refresh the token. I have to call it manually. Adding this before calling Authenticate solved my problem:

await credential.RefreshTokenAsync(CancellationToken.None);

Sorry to bother you.

@andyfurniss4 I haven't worked on this in months so I don't remember all the details, but I think all you need to do is add the RefreshTokenAsync line. Something like this (Sorry for the bad formatting):

var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync (
    clientSecrets,
    new[] { GmailService.Scope.MailGoogleCom },
    authentication.Email,
    cancellationToken);
cancellationToken.ThrowIfCancellationRequested ();
await credential.RefreshTokenAsync (cancellationToken); // *** This is the magic line ***
var oauth2 = new SaslMechanismOAuth2 (authentication.Email, credential.Token.AccessToken);
await client.AuthenticateAsync (oauth2, cancellationToken);

Thanks @jernelson7, I've figured it out in the end with your help and have come up with the following, which works for me.

var secrets = new ClientSecrets
	ClientId = Environment.GetEnvironmentVariable("GMailClientId"),
	ClientSecret = Environment.GetEnvironmentVariable("GMailClientSecret")
var googleCredentials = await GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, new[] { GmailService.Scope.MailGoogleCom }, email, CancellationToken.None);
if (googleCredentials.Token.IsExpired(SystemClock.Default))
	await googleCredentials.RefreshTokenAsync(CancellationToken.None);
using (var client = new SmtpClient())
	client.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
	var oauth2 = new SaslMechanismOAuth2(googleCredentials.UserId, googleCredentials.Token.AccessToken);
	client.Authenticate(oauth2);
	await client.SendAsync(message);
	client.Disconnect(true);
          

I know this is an old thread, but at what point during this process does the user give authorization for the application to access mail through a redirect to google? Or does this code assume that the user has already granted permission to the application at some earlier point, and that redirect to gain permission is handled in an unrelated process? I feel like I am missing a step here. Thanks!

@jordanshort I would recommend reading Google's developer docs to understand how OAuth2 works with their library.

All of your questions will be answered here: https://developers.google.com/identity/protocols/OAuth2

Once you have an oauth2 access_token, you can use it like this:

using (var client = new SmtpClient ()) {
	await client.ConnectAsync ("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
	var oauth2 = new SaslMechanismOAuth2 (googleCredentials.UserId, googleCredentials.Token.AccessToken);
	await client.AuthenticateAsync (oauth2);
	await client.SendAsync (message);
	await client.DisconnectAsync (true);

Here's Google's .NET API docs: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth

@jstedfast Thank you for your response, I've got to the point where I've completed the oauth process, I have an access_token and refresh_token, and everything looks good. However, on the client.AuthenticateAsync step I keep getting a strange error that I can't figure out: MailKit.Security.AuthenticationException: '334: eyJzdGF0dX....(what looks like a token)'. Here is my code:
`using (var client = new SmtpClient())
client.CheckCertificateRevocation = false;
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
await client.ConnectAsync(_smtpServer, 465, MailKit.Security.SecureSocketOptions.Auto);

            TokenResponse token = new TokenResponse
                AccessToken = _accessToken,
                RefreshToken = _refreshToken
            var clientSecrets = new ClientSecrets
                ClientId = "client_id",
                ClientSecret = "client_secret"
            IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
                ClientSecrets = clientSecrets,
                Scopes = new[] { "https://mail.google.com/", "profile", "email" }
            var credential = new UserCredential(flow, "me", token);
            if (credential.Token.IsExpired(SystemClock.Default))
                await credential.RefreshTokenAsync(CancellationToken.None);
            var oauth2 = new SaslMechanismOAuth2(credential.UserId, credential.Token.AccessToken);
            await client.AuthenticateAsync(oauth2);
            await client.SendAsync(mailMessage);
            await client.DisconnectAsync(true);
          

FWIW - I was able to find the solution to my particular problem. The string after the error code 334 was a base64 string. When I decoded that, it let me know that the token did not have the gmail scope. When I was initially generating the token, I was not getting the scope set correctly (done outside this body of code). Once I fixed that it started sending email just fine, so really not a mailkit issue.