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:
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:
I have the protocol log files available but they contain the access key so I will email them.
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.