emileverh 26 Posted February 9 Hi team! I have download the great github example from Geoffrey Smith https://github.com/geoffsmith82/GmailAuthSMTP/. So in short, I want to send (and send only) for clients who have a Google account And it worked fine. So I went to the verification approval process of Google and I passed 😉 I asked for the scope in Google Console: https://www.googleapis.com/auth/gmail.send because I want to send (and sending only) PDF's from my application (I have an invoicing app). But after the approval I get the error message "username and password not accepted". I looked at StackOverflow, Google, Deepseek, etc. to find an answer. I am spending already lots of time in this.... Anyone an idea? Share this post Link to post
eivindbakkestuen 47 Posted February 10 19 hours ago, emileverh said: I get the error message We can't see your screen, or your code... Share this post Link to post
emileverh 26 Posted February 10 1 hour ago, eivindbakkestuen said: We can't see your screen, or your code... procedure TdtmOAuth.DataModuleCreate(Sender: TObject); begin var inifilename: string := ChangeFileExt(ParamStr(0), '.ini'); FIniSettings := TIniFile.Create(inifilename); FOAuth2 := TEnhancedOAuth2Authenticator.Create(nil); IdHTTPServer.Active := True; end; procedure TdtmOAuth.DataModuleDestroy(Sender: TObject); begin FreeAndNil(FOAuth2); end; procedure TdtmOAuth.DoLog(const msg: String); begin CodeSite.Send(msg); end; function TdtmOAuth.HasRefreshToken: Boolean; begin Result := not FOAuth2.RefreshToken.IsEmpty; end; procedure TdtmOAuth.IdConnectionReceive(ASender: TIdConnectionIntercept; var ABuffer: TIdBytes); begin DoLog('R:' + TEncoding.ASCII.GetString(ABuffer)); end; procedure TdtmOAuth.IdConnectionSend(ASender: TIdConnectionIntercept; var ABuffer: TIdBytes); begin DoLog('S:' + TEncoding.ASCII.GetString(ABuffer)); end; procedure TdtmOAuth.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var code: string; begin if ARequestInfo.QueryParams = '' then Exit; var url: TURI := TURI.Create('https://localhost/?' + ARequestInfo.QueryParams); try code := url.ParameterByName['code']; except Exit; end; FOAuth2.AuthCode := code; FOAuth2.ChangeAuthCodeToAccesToken; // AResponseInfo.ContentText := '<html><body onload="setTimeout(window.close(), 5000);"><h1>PERIFACT: U kunt de browser nu sluiten en terugkeren naar het programma/ You can close the browser and return to the program</h1></body></html>'; AResponseInfo.ContentText := '<html><body><h1>PERIFACT: U kunt de browser nu sluiten en terugkeren naar het programma/ You can close the browser and return to the program</h1></body></html>'; var tokennamestr: string := Provider.AuthName + 'Token'; FIniSettings.WriteString('Authentication', tokennamestr, FOAuth2.RefreshToken); DoLog('Authenticated via OAUTH2'); DoLog(FOAuth2.RefreshToken); SetupAuthenticator; end; function TdtmOAuth.IsAuthenticated: Boolean; begin Result := FIsAuthenticated; end; procedure TdtmOAuth.AuthenticateInBrowser; begin var uri: TURI := TURI.Create(FOAuth2.AuthorizationRequestURI); ShellExecute(0, 'open', PChar(uri.ToString), nil, nil, 0); end; procedure TdtmOAuth.ClearAuthentication; begin // Delete persistent Refresh_token. Note // - This probably should have a logout function called on it // - The token should be stored in an encrypted way ... but this is just a demo. FIniSettings.DeleteKey('Authentication', Provider.TokenName); SetupAuthenticator; end; procedure TdtmOAuth.SendMessage; var IdMessage: TIdMessage; xoauthSASL: TIdSASLListEntry; begin // if we only have refresh_token or access token has expired // request new access_token to use with request FOAuth2.ClientID := Provider.ClientID; FOAuth2.ClientSecret := Provider.ClientSecret; FOAuth2.RefreshAccessTokenIfRequired; DoLog('refresh_token=' + FOAuth2.RefreshToken); DoLog('access_token=' + FOAuth2.AccessToken); if FOAuth2.AccessToken.Length = 0 then begin DoLog('Failed to authenticate properly'); Exit; end; IdSMTP.Host := Provider.SmtpHost; IdSMTP.UseTLS := Provider.TLS; IdSMTP.Port := Provider.SmtpPort; xoauthSASL := IdSMTP.SASLMechanisms.Add; xoauthSASL.SASL := Provider.AuthenticationType.Create(nil); TIdSASLOAuthBase(xoauthSASL.SASL).Token := FOAuth2.AccessToken; TIdSASLOAuthBase(xoauthSASL.SASL).User := Provider.ClientAccount; IdSSLIOHandlerSocketSMTP.SSLOptions.SSLVersions := [sslvTLSv1_2]; IdSMTP.Connect; IdSMTP.AuthType := satSASL; IdSMTP.Authenticate; // <<<<<<<<<<<< HERE IT FAILS!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<< IdMessage := TIdMessage.Create(Self); IdMessage.From.Address := Provider.ClientAccount; IdMessage.From.Name := CLIENTNAME; IdMessage.ReplyTo.EMailAddresses := IdMessage.From.Address; IdMessage.Recipients.Add.Text := CLIENTSENDTOADDRESS; IdMessage.Subject := 'Hello World'; IdMessage.Body.Text := 'Hello Body'; IdSMTP.Send(IdMessage); IdSMTP.Disconnect; ShowMessage('Message Sent'); end; Share this post Link to post
rvk 44 Posted February 10 On 2/9/2025 at 12:29 PM, emileverh said: So I went to the verification approval process of Google and I passed 😉 Did you also pass the "CASA Tier 2 security assessment" for your application? Can you provide the steps? I tried this a few years ago but they didn't provide any documentation as how to pass this. Tier 2 was only documented for online environments and Android Apps (where I would need to provide my app so they could test it). But I have an offline Windows application (which they don't seem to take into account). I finally gave up on the verification process. (I still get the "not verified" before entering the authentication screen) Or are you just in the test-phase where you can have a limited number of users (also normally used for internal testing)? (And also have the "not verified" screen) Regarding the error... I've only used the "AUTHENTICATE XOAUTH2" command with IMAP access to gMail. I see this library tries to do a user Bearer token. Did you try creating a new refresh-token (so going through the consent screen again)? Share this post Link to post
emileverh 26 Posted February 10 3 hours ago, rvk said: Did you also pass the "CASA Tier 2 security assessment" for your application? Can you provide the steps? I tried this a few years ago but they didn't provide any documentation as how to pass this. Tier 2 was only documented for online environments and Android Apps (where I would need to provide my app so they could test it). But I have an offline Windows application (which they don't seem to take into account). I finally gave up on the verification process. (I still get the "not verified" before entering the authentication screen) Or are you just in the test-phase where you can have a limited number of users (also normally used for internal testing)? (And also have the "not verified" screen) Regarding the error... I've only used the "AUTHENTICATE XOAUTH2" command with IMAP access to gMail. I see this library tries to do a user Bearer token. Did you try creating a new refresh-token (so going through the consent screen again)? CASA Tier 2.... I don't have a clue what that is. And I think also off topic. Yes, as I wrote, I am verified owner. I did 1000 times go to screen consent screen again. QUESTION.... ANYBODY: Is there a other way by not using the Indy components. Because gathering the tokens I tried today with a different component from DevExpress ( TdxGoogleAPIOauth2AuthorizationAgent). But I am stuck with 'IdSTMP.Authenticate' as you can see in the first post. In other words is there a working example for TNetHTTPClient?!?!? Share this post Link to post
rvk 44 Posted February 10 If you didn't go through the CASA Tier 2 then you are still in "Testing" phase. But that's ok and indeed perhaps off topic. Did you add scope https://www.googleapis.com/auth/gmail.send when asking for authentication? I know you said you added it to your consent screen but you also need to provide that scope (in your code) when going through the OAuth2 process. And second... when entering the consent screen, you need to CHECK the "Allow send" checkbox. It's not checked by default (and if you forget it, you won't have direct send access). I just did this myself again in my program and both my compose draft and send mail checkboxes where not checked by default. (I don't use the Indy components but Synapse but that shouldn't matter for above issues.) Share this post Link to post
rvk 44 Posted February 10 BTW. For the mentioned library... the same issue is described here: https://github.com/geoffsmith82/GmailAuthSMTP/issues/19 Share this post Link to post
emileverh 26 Posted February 10 1 hour ago, rvk said: If you didn't go through the CASA Tier 2 then you are still in "Testing" phase. But that's ok and indeed perhaps off topic. Did you add scope https://www.googleapis.com/auth/gmail.send when asking for authentication? I know you said you added it to your consent screen but you also need to provide that scope (in your code) when going through the OAuth2 process. And second... when entering the consent screen, you need to CHECK the "Allow send" checkbox. It's not checked by default (and if you forget it, you won't have direct send access). I just did this myself again in my program and both my compose draft and send mail checkboxes where not checked by default. (I don't use the Indy components but Synapse but that shouldn't matter for above issues.) Yes scope is correct. Else I did not get the final approval. @Remy Lebeau You as a specialist. Do you know if we can get it working via other components such as TNetHTTPClient? So not for the full scope but for the send-only scope. If you have any good tips for Indy, that is of course also welcome! ( https://www.googleapis.com/auth/gmail.send ) Share this post Link to post
Remy Lebeau 1494 Posted February 10 4 minutes ago, emileverh said: If you have any good tips for Indy, that is of course also welcome! ( https://www.googleapis.com/auth/gmail.send ) Indy recently got a few new OAuth components that work with SMTP/POP3/IMAP4 SASL-based authentication. However, getting/refreshing a token is up to you to handle separately via each OAuth provider's API (via HTTP/REST, etc), but once you have a token then Indy can login with it. Geoffrey Smith's example predates those components, but he handles both REST and SASL portions. Indy doesn't have any components to handle the REST portion for you, but its TIdHTTP component can certain be used to send REST requests. Share this post Link to post
emileverh 26 Posted February 10 Just now, Remy Lebeau said: Indy recently got a few new OAuth components that work with SMTP/POP3/IMAP4 SASL-based authentication. However, getting/refreshing a token is up to you to handle separately via each OAuth provider's API (via HTTP/REST, etc), but once you have a token then Indy can login with it. Geoffrey Smith's example predates those components, but he handles both REST and SASL portions. Indy doesn't have any components to handle the REST portion for you, but its TIdHTTP component can certain be used to send REST requests. "...Indy recently got a few new OAuth components...." In Delphi 12.2? Share this post Link to post
rvk 44 Posted February 10 44 minutes ago, emileverh said: So not for the full scope but for the send-only scope. If you have any good tips for Indy, that is of course also welcome! ( https://www.googleapis.com/auth/gmail.send ) I'm beginning to think that using the limited scope gmail.send isn't allowed for SMTP sending. Quote The scope for IMAP, POP, and SMTP access is https://mail.google.com/. If you request access to the full mail scope for your IMAP, POP or SMTP app, it must be in compliance with our Google API Services: User Data Policy. To be approved, your app must show full utilization of https://mail.google.com/. If your app doesn’t require https://mail.google.com/, migrate to the Gmail API and use more granular restricted scopes. https://developers.google.com/gmail/imap/xoauth2-protocol There you see the remark (second point) "migrate to the Gmail API and use more granular restricted scopes". So for the limited scopes you would need to use the GMail API and not the SMTP service (?) For SMTP access I still use App passwords (which still work fine). For OAuth2 access with limited gmail.send and gmail.compose I use the GMail API at https://gmail.googleapis.com/upload/gmail/v1/users/{userId}/drafts/send (with attachments). You can either use drafts/send or messages/send method via the GMail API. https://developers.google.com/gmail/api/guides/sending Maybe someone else can confirm the above (that gmail.send scope can't be used with the smtp service). I already see here that it is confirmed on stackoverflow. https://stackoverflow.com/questions/39161914/using-gmail-send-scope-with-smtp-msa Quote The send scope is only documented to work on the Gmail API (HTTP REST interface). If you want to use SMTP yes, it only accepts the full mail scope. Share this post Link to post
Remy Lebeau 1494 Posted February 10 (edited) 2 hours ago, emileverh said: "...Indy recently got a few new OAuth components...." In Delphi 12.2? No, they were merged into Indy's master code about a month after the release of 12.2. https://www.indyproject.org/2024/10/20/sasl-oauth-support-finally-added-to-master/ Edited February 10 by Remy Lebeau 1 Share this post Link to post