0

I'm trying to use Mailkit to get emails from my Gmail:

private readonly string[] Scopes = { GmailService.Scope.GmailReadonly };
    private UserCredential GetGmailCredential()
    {
        UserCredential credential;
        using (var stream = new FileStream("client_id.json", FileMode.Open, FileAccess.Read))
        {
            // The file token.json stores the user's access and refresh tokens, and is created
            // automatically when the authorization flow completes for the first time.
            string credPath = "token.json";
            credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.Load(stream).Secrets,
                Scopes,
                "user",
                CancellationToken.None,
                new FileDataStore(credPath, true),
                new LocalServerCodeReceiver()).Result;
            Console.WriteLine($"Credential file saved to: {credPath}");
        }

        return credential;
    }

    [HttpGet("check")]
    public string GetD()
    {
        using (var client = new ImapClient())
        {
            client.Connect("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect);
            UserCredential credential = GetGmailCredential();
            //var oauth2 = new SaslMechanismOAuth2("myaccount008@gmail.com", credential.Token.AccessToken);
            var oauth2 = new SaslMechanismOAuth2("myaccount008",credential.Token.AccessToken);
            client.Authenticate(oauth2);
            client.Inbox.Open(FolderAccess.ReadOnly);

            var uids = client.Inbox.Search(SearchQuery.FromContains("bill"));
            string subjects = string.Empty;

            foreach (var uid in uids)
            {
                var message = client.Inbox.GetMessage(uid);
                subjects += message.Subject + Environment.NewLine;
                // write the message to a file
                message.WriteTo(string.Format("{0}.eml", uid));
            }

            client.Disconnect(true);
            return subjects;
        }
    }

Line: client.Authenticate(oauth2); throw exception ->

enter image description here

MailKit.Security.AuthenticationException

I followed this answer to setup my Gmail API, but then I got the Authentication Failed issue.

Update 1

Here is the log:

Connected to imaps://imap.gmail.com:993/ S: * OK Gimap ready for
requests from 122.199.45.135 d3mb920463124pjs C: A00000000 CAPABILITY
S: * CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST
CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN
AUTH=PLAIN-CLIENTTOKEN AUTH=OAUTHBEARER AUTH=XOAUTH S: A00000000 OK
Thats all she wrote! d3mb920463124pjs C: A00000001 AUTHENTICATE
XOAUTH2
dXNlcj1mcmFudmEwMDhAZ21haWwuY29tAWF1dGg9QmVhcmVyIHlhMjkuYTBBZHcxeGVWZDNSWWwzcVZlblZwVm1MbDBRRVVyWkdxd05veEd0QWpcLVhHNjRBaF90eWM0NWhwSFprZHA0d3dPWlpGMVZwbGM3dGo1Tm80eVMwc2lPNE1VYmhHV1I1WE9sdWtLUGY4TF9QU0dpZjhrSWM2UXNUbTQwYjlweDNBeXE4bVYtOTM2akEtSHdXekNQVFdGMGk0NGozX2FOTmhEYk5rAQE=
S: +
eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoPQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==
C:  S: A00000001 NO [AUTHENTICATIONFAILED] Invalid credentials
(Failure)

The scope, I use: GmailService.Scope.GmailReadonly as I only need to retrieve emails.

Update 2

Updated my scope to:

private readonly string[] Scopes = { GmailService.Scope.MailGoogleCom };
Connected to imaps://imap.gmail.com:993/
S: * OK Gimap ready for requests from 122.199.45.135 s90mb321411106pjc
C: A00000000 CAPABILITY.
S: * CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENTTOKEN AUTH=OAUTHBEARER AUTH=XOAUTH
S: A00000000 OK Thats all she wrote! s90mb321411106pjc
C: A00000001 AUTHENTICATE XOAUTH2 dXNlcj1mcmFudmEwMDhAZ21haWwuY29tAWF1dGg9QmVhcmVyIHlhMjkuYTBBZHcxeGVWZDNSWWwzcVZlblZwVm1MbDBRRVVyWkdxd05veEd0QWpXLVhHNjRBaF90eWM0NWhwSFprZHA0d3dPWlpGMVZwbGM3dGo1Tm80eVMwc2lPNE1VYmhHV1I1WE9sdWtLUGY4TF9QU0dpZjhrSWM2UXNUbTQwYjlweDNBeXE4bVYtOTM2akEtSHdXekNQVFdGMGk0NGozX2FPTmhEYk5rAQE=
S: +
eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiSmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==
C: 
S: A00000001 NO [AUTHENTICATIONFAILED] Invalid credentials (Failure)

Update 3

I keep everything same and also tried to use GmailService.Scope.ReadOnly then replaced ImapClient with the following code, now it works.

      // Create Gmail API service.
        service = new GmailService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,
            ApplicationName = ApplicationName,
        });

This piece of code is from the Gmail Api. It would be great if I could use Mailkit to do the authentication.

jstedfast
  • 35,744
  • 5
  • 97
  • 110
Franva
  • 6,565
  • 23
  • 79
  • 144
  • 1
    9 times out of 10, the problem is because you are using the wrong scope(s) and/or because you are using the wrong username string in the AuthorizeAsync() method. Also `new SaslMechanismOAuth2("myaccount008",credential.Token.AccessToken);` makes it look like you are not adding the `@gmail.com` to the account name and you need to do that. Get a protocol log and base64 decode the error message that the server returns when it fails. – jstedfast Mar 16 '20 at 15:42
  • hi @jstedfast thanks for your help, I have also tried the account with "at gmail", still doesn't work. I also included my Scope. As to the log, how to do a protocol log?? I have been stuck here for hours, and my brain has stopped work >_<; – Franva Mar 16 '20 at 15:56
  • `new ImapClient (new ProtocolLogger ("imap.log"))` will dump a protocol log to `imap.log` or whatever file name you use. "Also included my Scope" - what does that mean? What is the scope value? That's what I need to see. – jstedfast Mar 16 '20 at 16:51
  • This is the scope you need: `Scopes = new string[] { GmailService.Scope.MailGoogleCom }` – jstedfast Mar 16 '20 at 16:52
  • hi @jstedfast thanks for your help. I have updated my question. in the section Update, you will see the log and my Scope. Just wondering do I need to verify my app/project before using the credentials? – Franva Mar 17 '20 at 11:16
  • Feedback (1/2): updates should always be made at the end of a question, in chronological order. Please consider new readers when writing your questions - it is not normal to read from the end and work upwards. – halfer Mar 18 '20 at 16:39
  • Feedback (2/2). Please refrain from adding "please help" begging messages to your questions. I have downvoted for this as a helpful reminder. Readers know that you want help by virtue of you posting here, and they do not need to see pleading of any kind, especially if it is added to every question. Readers are mostly volunteers here, and they should not be subject to coercive language of any kind. – halfer Mar 18 '20 at 16:41
  • 1
    thanks @halfer suggestion accepted. – Franva Mar 18 '20 at 22:41

1 Answers1

2

Part of the problem is that you are using the wrong scope. You need to use GoogleService.Scope.MailGoogleCom.

The scope you are using is not for IMAP or POP3 access, it only works for Google’s web request API.

The following code works for me:

const string GMailAccount = "username@gmail.com";

var clientSecrets = new ClientSecrets {
    ClientId = "XXX.apps.googleusercontent.com",
    ClientSecret = "XXX"
};

var codeFlow = new GoogleAuthorizationCodeFlow (new GoogleAuthorizationCodeFlow.Initializer {
    DataStore = new FileDataStore ("CredentialCacheFolder", false),
    Scopes = new [] { "https://mail.google.com/" },
    ClientSecrets = clientSecrets
});

var codeReceiver = new LocalServerCodeReceiver ();
var authCode = new AuthorizationCodeInstalledApp (codeFlow, codeReceiver);
var credential = await authCode.AuthorizeAsync (GMailAccount, CancellationToken.None);

if (authCode.ShouldRequestAuthorizationCode (credential.Token))
    await credential.RefreshTokenAsync (CancellationToken.None);

var oauth2 = new SaslMechanismOAuth2 (credential.UserId, credential.Token.AccessToken);

using (var client = new ImapClient ()) {
    await client.ConnectAsync ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect);
    await client.AuthenticateAsync (oauth2);
    await client.DisconnectAsync (true);
}
jstedfast
  • 35,744
  • 5
  • 97
  • 110
  • updated with GmailService.Scope.MailGoogleCom, the error persists and it's the same error. I have included the log in my Update 2. I just wondering do I need to verify my app/project on Google before I can use it? I should not need to do this as this is just a development stage, not production. – Franva Mar 17 '20 at 11:46
  • hi @jstedfast , I replaced the ImapClient with Google's Gmail Api code and now I can connect to my gmail. See my Update 3 – Franva Mar 17 '20 at 12:22
  • It doesn't matter if your app is in the development phase instead of production, if Google wants you to verify your app, you need to verify your app. – jstedfast Mar 17 '20 at 13:34
  • true. but if that's the case then the Gmail API code should also fail to retrieve emails, but it doesn't. So I am running out of ideas to figure out how to use MailKit to retrieve emails from Gmail. – Franva Mar 18 '20 at 01:12
  • also, I'm trying to use the code from MailKit's sample code, but they are not working. Then I tried to add reference to the Mailkit.NetStandard project to do further debug and found this project depends on .net framework which I thought it should be a .Net Core project and have no dependencies on the old .Net Framework. Any help is appreciated. – Franva Mar 18 '20 at 01:16
  • The MailKit.NetStandard.csproj project is a multi target project that targets both .NETStandard and .NETFramework. Same with the MimeKit.NetStandard.csproj that it references. – jstedfast Mar 18 '20 at 11:38
  • The Google docs say that app validation is required for some scopes, mail.google.com being one of them. – jstedfast Mar 18 '20 at 16:00
  • 1
    I was able to get it working just fine w/o app validation, but I also didn't set an icon. Here are my instructions: https://github.com/jstedfast/MailKit/blob/master/GMailOAuth2.md – jstedfast Mar 18 '20 at 17:23
  • brilliant~! I tried it and this code works~!!! Only 1 thing in this piece of code doesn't work which is the FileDataStore, it doesn't create a folder called: CredentialCacheFolder, don't why. Also, could you please point out where you found the code to use "GoogleAuthorizationCodeFlow" and Scope? I will need to know where to find them just in case I need any extra information so that I could find it myself next time. Once again, thank you for your super detailed steps and code. Thumb up~! – Franva Mar 19 '20 at 07:37
  • The cache folder is created somewhere under your user AppData directory. If you want it created in the bin directory, pass true instead of false. I couldn’t find any good samples of how to use Google’s C# API so I just played with it. – jstedfast Mar 19 '20 at 09:11
  • thanks man. I tried to play with it and failed. clearly I need to learn more about digging :) appreciate for your help and making the brilliant MailKit. – Franva Mar 19 '20 at 11:56
  • hi there, Google just updated its Gmail API and now the working code stopped working. Could you please help: https://stackoverflow.com/questions/60830442/how-to-use-mailkit-to-retrieve-emails-from-gmail – Franva Mar 24 '20 at 11:48
  • Just call `await credential.RefreshTokenAsync (CancellationToken.None);` – jstedfast Mar 24 '20 at 13:16
  • we have that code in your demo code, don't we? ```if (authCode.ShouldRequestAuthorizationCode (credential.Token)) await credential.RefreshTokenAsync (CancellationToken.None);``` – Franva Mar 25 '20 at 11:30
  • The if-statement that I came up with may be wrong, I don't know, because the token I had wasn't expired yet. Look for another way to check if the token needs to be refreshed. – jstedfast Mar 25 '20 at 14:51
  • thanks @jstedfast, managed to get it work after I deleted the token folder, OAuth credential from Google API. I guess it might be because of the expired token. – Franva Mar 26 '20 at 03:33
  • @jstedfast, I am getting an oauth2 token using SaslMechanismOAuth2. But "Authentication failed" exception throwing from [await client.AuthenticateAsync (oauth2);] this line. – Prashant Girase May 16 '22 at 08:32
  • Then you either passed in the wrong user name or your google token expired before you used it – jstedfast May 16 '22 at 11:59
  • @jstedfast I am using username="sd637238@gmail.com", and I also checked the token is not expired. I already enabled the IMAP option from Gmail settings. If I enabled "less security options" then using without XOAUTH2 it means [client.AuthenticateAsync("sd637238@gmail.com", "Reset@123")] then working fine. But I want to fetch emails without enabling "less security options" using username & token. – Prashant Girase May 16 '22 at 14:36
  • @PrashantGirase make sure you are using credential. UserId and not a hard-coded string. – jstedfast May 16 '22 at 17:37
  • @jstedfast, I used the same above code. I used my ClientSecret & ClientId. Below exception getting **MailKit.Net.Imap.ImapClient.d__101.MoveNext() ... .ExceptionServices.ExceptionDispatchInfo.Throw() ... CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) .. CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) .. CompilerServices.TaskAwaiter.GetResult()** and **A00000001 NO [AUTHENTICATIONFAILED] Invalid credentials (Failure)** this log it – Prashant Girase May 17 '22 at 04:56
  • The failure is coming from the server so it must be rejecting your token for some reason. – jstedfast May 17 '22 at 12:22