HeartWare 1 Posted April 21, 2023 I currently have the following code: Quote procedure TForm141.ConnectBtnClick(Sender: TObject); VAR IMAP : TIdIMAP4; SSL : TIdSSLIOHandlerSocketOpenSSL; BEGIN IMAP:=TIdIMAP4.Create(NIL); TRY SSL:=TIdSSLIOHandlerSocketOpenSSL.Create(IMAP); IMAP.Host:=Host.Text; IMAP.Username:=UserName.Text; IMAP.Password:=Password.Text; IMAP.IOHandler:=SSL; IMAP.AuthType:=TIdIMAP4AuthenticationType.iatUserPass; IMAP.UseTLS:=TIdUseTLS.utUseImplicitTLS; SSL.SSLOptions.SSLVersions:=[TIdSSLVersion.sslvTLSv1_2]; IMAP.Port:=993; IMAP.Connect; TRY Information('Messages: '+IntToStr(IMAP.MailBox.TotalMsgs)) FINALLY IMAP.Disconnect END FINALLY FreeAndNIL(IMAP) END END; but when I try to connect to Microsoft Office365 (at outlook.office365.com) I can't get it to work (get "LOGIN failed" exception). Is there some other property of the TIdIMAP that I need to set? And what exactly should I put into the UserName/Password fields? Anyone have some working code that connects to outlook.office365.com using TIdIMAP4 ? Keld R. Hansen Share this post Link to post
Remy Lebeau 1397 Posted April 21, 2023 Outlook365 requires OAuth authentication: https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth At this time, Indy does not have any components for retrieving an access token from an OAuth provider, so you will have to handle that yourself manually per the above documentation, such as with TIdHTTP. However, there is currently a sasl-oauth branch in Indy's GitHub repo (it hasn't been merged into the main code yet) which adds a few new TIdSASL components for sending OAuth access tokens for authentication in POP3, SMTP, IMAP, and DICT protocols. So, in this case, you could add the TIdSASLXOAuth2 component to the TIdIMAP4.SASLMechanisms property, and then use the OAuth access token as the password. 1 Share this post Link to post
HeartWare 1 Posted April 25, 2023 (edited) Thank you, Remy. I have downloaded the files, explicitly added the necessary .PAS files to my project (to force the use of these instead of the Indy10 in my RAD Studio installation), and added the following lines: Quote SASL:=TIdSASLXOAuth2.Create(IMAP); // What do I need to initialize in SASL ? IMAP.SASLMechanisms.Add.SASL:=SASL; after the "UseTLS" line. I assume I need to assign something to SASL.UserPassProvider and/or call some of the Autheticate functions. But what should I put into the parameters of the Authenticate calls? Host/Protocol I assume will be outlook.office365.com and 993 (?) but what about "ProtocolName" and what do I send/receive in VirtualResponse? Where do I put the base64 encoded string with user and bearer token? In OnGetAccessToken, I assume? What final value from SASL should I assign to IMAP.Password after authentication? Or does that happen internally, due to my adding SASL to the IMAP's SASLMechanisms? Can you help me out, please? 🙂 Edited April 25, 2023 by HeartWare Share this post Link to post
Remy Lebeau 1397 Posted April 25, 2023 (edited) 9 hours ago, HeartWare said: I assume I need to assign something to SASL.UserPassProvider and/or call some of the Autheticate functions. No, you do not call the Authenticate methods directly. Yes, you need to assign a TIdUserPassProvider object to TIdSASLXOAuth2.UserProvider, and then assign the IMAP username to TIdUserPassProvider.Username, and either 1) assign the OAuth access token to TIdUserPassProvider.Password, or 2) set the TIdUserPassProvider.Password to a blank string and instead assign a handler to the TIdSASLXOAuth2.OnGetAccessToken event to return the access token. Then, simply set TIdIMAP4.AuthType to iatSASL, and call TIdIMAP4.Connect() with AAutoLogin=True (which is the default behavior), or call Connect() with AAutoLogin=False and then call TIdIMAP4.Login() when needed. Quote But what should I put into the parameters of the Authenticate calls? You don't call them yourself at all. Quote Host/Protocol I assume will be outlook.office365.com and 993 (?) but what about "ProtocolName" and what do I send/receive in VirtualResponse? Where do I put the base64 encoded string with user and bearer token? In OnGetAccessToken, I assume? No to all of that. TIdIMAP4 and TIdSASLXOAuth2 will handle all of that detail internally for you. All you have to provide is the access token, as given to you by the OAuth provider. Quote What final value from SASL should I assign to IMAP.Password after authentication? Just the OAuth access token. But it needs to be provided by TIdUserPassProvider.Password or TIdSASLXOAuth2.OnGetAccessToken, not by TIdIMAP4.Password. The TIdIMAP4.Username and TIdIMAP4.Password properties are used only when TIdIMAP4.AuthType=iatUserPass. Quote Or does that happen internally, due to my adding SASL to the IMAP's SASLMechanisms? Yes. Edited April 25, 2023 by Remy Lebeau Share this post Link to post
HeartWare 1 Posted April 26, 2023 (edited) Thank you very much, Remy. I have now come a bit longer, but it still fails (maybe I'm feeding it the wrong "secret key"). I have this code now: Quote PROCEDURE TForm141.ConnectBtnClick(Sender: TObject); VAR IMAP : TIdIMAP4; SSL : TIdSSLIOHandlerSocketOpenSSL; PASS : TIdUserPassProvider; SASL : TIdSASLXOAuth2; BEGIN IMAP:=TIdIMAP4.Create(NIL); TRY IMAP.Host:=Host.Text; IMAP.AuthType:=TIdIMAP4AuthenticationType.iatSASL; SSL:=TIdSSLIOHandlerSocketOpenSSL.Create(IMAP); IMAP.IOHandler:=SSL; IMAP.UseTLS:=TIdUseTLS.utUseImplicitTLS; PASS:=TIdUserPassProvider.Create(IMAP); PASS.Username:=UserName.Text; PASS.Password:=SecretKey; SASL:=TIdSASLXOAuth2.Create(IMAP); SASL.UserPassProvider:=PASS; IMAP.SASLMechanisms.Add.SASL:=SASL; SSL.SSLOptions.SSLVersions:=[TIdSSLVersion.sslvTLSv1_2]; IMAP.Port:=993; IMAP.Connect; TRY Information('Messages: '+IntToStr(IMAP.MailBox.TotalMsgs)) FINALLY IMAP.Disconnect END FINALLY FreeAndNIL(IMAP) END END; but it fails in IdIMAP4.PAS at this line: Quote function PerformSASLLogin_IMAP(ASASL: TIdSASL; AEncoder: TIdEncoder; ADecoder: TIdDecoder; AClient : TIdIMAP4): Boolean; const AOkReplies: array[0..0] of string = (IMAP_OK); AContinueReplies: array[0..0] of string = (IMAP_CONT); var S: String; AuthStarted: Boolean; begin Result := False; AuthStarted := False; // TODO: use UTF-8 when base64-encoding strings... if AClient.IsCapabilityListed('SASL-IR') then begin {Do not localize} if ASASL.TryStartAuthenticate(AClient.Host, AClient.Port, IdGSKSSN_imap, S) then begin AClient.SendCmd(AClient.NewCmdCounter, 'AUTHENTICATE ' + String(ASASL.ServiceName) + ' ' + AEncoder.Encode(S), [], True); {Do not Localize} if CheckStrFail(AClient.LastCmdResult.Code, AOkReplies, AContinueReplies) then begin ASASL.FinishAuthenticate; Exit; // this mechanism is not supported // <--- Exits here end; AuthStarted := True; end; end; And ASASL.Service = 'XOAUTH2' and S = 'user=<MailBox@domain>'#1'auth=Bearer <Secret Key>'#1#1 and AClient.LastCmdResult.Code = 'NO' When I generated my tokens/keys according to Outlook documentation, I got two values. I have tried with both of them, and they both fail at this spot. Is there a way where I can generate the token needed in code somehow, or do I need to use the value I was given via the online web system? Edited April 26, 2023 by HeartWare Share this post Link to post
HeartWare 1 Posted April 26, 2023 This is the communication log (>> is me sending, << is receiveing) Quote << * OK The Microsoft Exchange IMAP4 service is ready. [TQBNADAAUAAyADgAMABDAEEAMAAwADgAMAAuAFMAVwBFAFAAMgA4ADAALgBQAFIATwBEAC4ATwBVAFQATABPAE8ASwAuAEMATwBNAA==] >> C1 CAPABILITY << * CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+ << C1 OK CAPABILITY completed. >> C2 AUTHENTICATE XOAUTH2 dXNlcj1ib3VuY2VAYWRtaW5kLmRrAWF1d******DQyLWM4YzEtNGJhYy1iN2Y1LTQxN2Y4ZmZlOTc0ZAEB << C2 NO AUTHENTICATE failed. >> C3 LOGOUT << * BYE Microsoft Exchange Server IMAP4 server signing off. << C3 OK LOGOUT completed. Share this post Link to post
Remy Lebeau 1397 Posted April 26, 2023 (edited) 10 hours ago, HeartWare said: When I generated my tokens/keys according to Outlook documentation, I got two values. I have tried with both of them, and they both fail at this spot. You are not supposed to use your secret keys as-is for the login token. You are supposed to use the keys to request an access token dynamically, and then use that token instead for login. NEVER give out your secret keys! Quote Is there a way where I can generate the token needed in code somehow, or do I need to use the value I was given via the online web system? Did you read the documentation I linked to earlier? Authenticate an IMAP, POP or SMTP connection using OAuth Quote You can use the OAuth authentication service provided by Azure Active Directory (Azure AD) to enable your application to connect with IMAP, POP or SMTP protocols to access Exchange Online in Office 365. To use OAuth with your application, you need to: Register your application with Azure AD. Get an access token from a token server. Authenticate connection requests with an access token. In particular, make sure you read the section titled "Get an access token". You are responsible for handling Steps 1 (which presumably you have already done) and Step 2 (the piece that is currently missing), and TIdIMAP4/TIdSASLXOuth2 will handle step 3 for you. Edited April 26, 2023 by Remy Lebeau Share this post Link to post