![]() |
Indy und OAuth / Microsoft365
Hallo Zusammen,
ich versuche mich an microsoft365 und SMTP mit OAuth. Habe nach diversen Anleitungen eine App dort angelegt und entsprechende Rechte eingerichtet. Aktuell beziehe ich einen Token über eine Komponenten von ![]() das klappt soweit. Was nicht klappt ist der Indy SMTP Teil. Hier die Funktion mit der per OAuth eine Mail gesendet werden soll.
Delphi-Quellcode:
Der Token sieht wie folgt aus (gekürzt)
procedure TForm1.SendSMTP(ptToken:String);
Var lcSMTP:TIdSMTP; lcMail:TIdMessage; lcRec:TIdEMailAddressItem; ltAuth:String; lcAuthSASL:TIdSASLListEntry; lcIdSSLIOHandlerSocketSMTP:TidSSLIOHandlerSocketOpenSSL; lcUserPass:TIdUserPassProvider; lcDebug:TIdLogDebug; begin lcSMTP:=TIdSMTP.Create; Try lcSMTP.Host:='smtp.office365.com'; lcSMTP.Port:=587; lcMail:=TIdMessage.Create(lcSMTP); Try lcMail.Encoding:=meMime; lcRec:=lcMail.Recipients.Add; lcRec.Address:='XXX@YYY.de'; lcRec.Name:='XXX@YYY.de'; lcMail.Subject:='Test'; lcMail.Body.Text:='Testnachricht via OAuth'; lcIdSSLIOHandlerSocketSMTP:=TidSSLIOHandlerSocketOpenSSL.Create(lcSMTP); lcIdSSLIOHandlerSocketSMTP.SSLOptions.SSLVersions:=[sslvTLSv1_2]; lcUserPass:=TIdUserPassProvider.Create(lcSMTP); lcUserPass.Username:='myname@test.onmicrosoft.com'; lcUserPass.Password:=ptToken; lcAuthSASL:=lcSMTP.SASLMechanisms.Add; lcAuthSASL.SASL:=TIdSASLXOAuth2.Create(lcSMTP); TIdSASLXOAuth2(lcAuthSASL.SASL).UserPassProvider:=lcUserPass; lcDebug:=TIdLogDebug.Create(lcSMTP); lcDebug.OnReceive:=DoAddLogReceive; lcDebug.OnSend:=DoAddLogSend; lcSMTP.Intercept:=lcDebug; lcSMTP.AuthType:=satSASL; lcSMTP.Connect; -------------------------------------- - hier wird eine Exception ausgelöst - -------------------------------------- lcSMTP.Authenticate; lcSMTP.Send(lcMail); Finally lcMail.Free; End; lcSMTP.Disconnect; Finally lcSMTP.Free; End; end;
Code:
Über IdLogDebug kommen folgende Ausgaben:
{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0eXAiOiJKV1Q...iLCJub}
Zitat:
Aber was kann der Grund dafür sein ? Ist der Token nicht korrekt ? Nutze ich Indy an der Stelle falsch ? Vielen Dank für eure Hilfe. |
AW: Indy und OAuth / Microsoft365
Funktioniert es denn z.B. mit Telnet?
Mit der Kombination Telnet + OpenSSL habe ich es bishe rnoch nicht zu tun gehabt, aber es ist anscheinend möglich: ![]() ![]() Alternativ geht es eventuell mit einem einfachen Client in einer anderen gängigen Programmiersprache, z.B. Python: ![]() Zitat:
|
AW: Indy und OAuth / Microsoft365
Habe nochmal etwas gefunden und bin jetzt einen Schritt weiter.
Delphi-Quellcode:
Damit bekomme ich folgende Log Einträge:
procedure SendSMTP(ptToken:String);
Var lcSMTP:TIdSMTP; lcMail:TIdMessage; lcRec:TIdEMailAddressItem; lcAuthSASL:TIdSASLListEntry; lcProvider:TProviderInfo; lcIdSSLIOHandlerSocketSMTP:TidSSLIOHandlerSocketOpenSSL; lcUserPass:TIdUserPassProvider; lcDebug:TIdLogDebug; begin ctToken:=ptToken; lcSMTP:=TIdSMTP.Create; Try lcSMTP.Host:='smtp.office365.com'; lcSMTP.Port:=587; lcMail:=TIdMessage.Create(lcSMTP); Try lcMail.Encoding:=meMime; lcRec:=lcMail.Recipients.Add; lcRec.Address:='xxx@yyy.de'; lcRec.Name:='xxx@yyy.de'; lcMail.Subject:='Test'; lcMail.Body.Text:='Testnachricht via OAuth'; lcIdSSLIOHandlerSocketSMTP:=TidSSLIOHandlerSocketOpenSSL.Create(lcSMTP); lcIdSSLIOHandlerSocketSMTP.SSLOptions.SSLVersions:=[sslvTLSv1_2]; lcUserPass:=TIdUserPassProvider.Create(lcSMTP); lcUserPass.Username:='MyMail@test.onmicrosoft.com'; lcUserPass.Password:=ptToken; lcAuthSASL:=lcSMTP.SASLMechanisms.Add; lcAuthSASL.SASL:=TIdSASLXOAuth2.Create(lcSMTP); TIdSASLXOAuth2(lcAuthSASL.SASL).UserPassProvider:=lcUserPass; lcDebug:=TIdLogDebug.Create(lcSMTP); lcDebug.OnReceive:=DoAddLogReceive; lcDebug.OnSend:=DoAddLogSend; lcSMTP.Intercept:=lcDebug; lcSMTP.IOHandler:=lcIdSSLIOHandlerSocketSMTP; lcSMTP.UseTLS:=utUseExplicitTLS; lcSMTP.AuthType:=satSASL; lcSMTP.Connect; lcSMTP.Authenticate; lcSMTP.Send(lcMail); Finally lcMail.Free; End; lcSMTP.Disconnect; Finally lcSMTP.Free; End; end; Zitat:
eingerichtet, der SMTP Zugriff auf das Postfach ist erlaubt. Hat noch jemand eine Idee ? |
AW: Indy und OAuth / Microsoft365
Ich kann morgen mal schauen wie ich es mache.
Habe den Laptop gerade nicht hier. Aber dem Benutzer wurde die Rechte gegeben per SMTP zu Senden? Das ist beim Account bei der Arbeit default aus gewesen. Und beim Privaten Live/Hotmail war das schon erlaubt |
AW: Indy und OAuth / Microsoft365
Zitat:
Ist das bei dir "ptToken"? Bin mir nicht sicher, aber probiere Mal:
Code:
Bearer eyJ0eXAiOiJKV1Q...iLCJub
|
AW: Indy und OAuth / Microsoft365
Zitat:
Ich würde es gerne testen, habe aber meine 30 Tage für kostenlose Tests bereits aufgebraucht. Benötigt der Versand einen (virtuellen) MS Exchange Server? |
AW: Indy und OAuth / Microsoft365
Was Olli schreibt: du sendest nicht das Token, sondern du sendest die gesamte Antwort, also das gesamte JSON, des Authentifizierungsdienstes. Das Access-Token, das du senden musst, ist nur das, was in der Eigenschaft "access_token" steht.
|
AW: Indy und OAuth / Microsoft365
ist die TIdSASLXOAuth2 in Indy10\Protocols mitlerweile schon enthalten?
diese geht
Delphi-Quellcode:
unit IdSASLXOAUTH;
interface uses Classes, IdSASL ; type TIdSASLXOAuth = class(TIdSASL) private FToken: string; FUser: string; FTwoLinePopFormat: Boolean; public property Token: string read FToken write FToken; property User: string read FUser write FUser; property TwoLinePopFormat: Boolean read FTwoLinePopFormat write FTwoLinePopFormat; class function ServiceName: TIdSASLServiceName; override; constructor Create(AOwner: TComponent); destructor Destroy; override; function TryStartAuthenticate(const AHost, AProtocolName : String; var VInitialResponse: String): Boolean; override; function ContinueAuthenticate(const ALastResponse, AHost, AProtocolName : string): string; override; function StartAuthenticate(const AChallenge, AHost, AProtocolName: string): string; override; { For cleaning up after Authentication } procedure FinishAuthenticate; override; end; implementation { TIdSASLXOAuth } class function TIdSASLXOAuth.ServiceName: TIdSASLServiceName; begin Result := 'XOAUTH2'; end; constructor TIdSASLXOAuth.Create(AOwner: TComponent); begin inherited; end; destructor TIdSASLXOAuth.Destroy; begin inherited; end; function TIdSASLXOAuth.TryStartAuthenticate(const AHost, AProtocolName: String; var VInitialResponse: String): Boolean; begin if (AProtocolName = 'pop') and FTwoLinePopFormat then begin // Don't send anything yet end else begin VInitialResponse := 'user=' + FUser + Chr($01) + 'auth=Bearer ' + FToken + Chr($01) + Chr($01); end; Result := True; end; function TIdSASLXOAuth.StartAuthenticate(const AChallenge, AHost, AProtocolName: string): string; begin Result := 'user=' + FUser + Chr($01) + 'auth=Bearer ' + FToken + Chr($01) + Chr($01); end; function TIdSASLXOAuth.ContinueAuthenticate(const ALastResponse, AHost, AProtocolName: string): string; begin // Nothing to do end; procedure TIdSASLXOAuth.FinishAuthenticate; begin // Nothing to do end; end.
Delphi-Quellcode:
IdSASLXOAUTH := TIdSASLXOAuth.Create(SMTP);
IdSASLXOAUTH.Token := FToken; // ca. 2300 Zeichen lang bei Microsoft IdSASLXOAUTH.User := FBenutzername; IdSASLXOAUTH.TwoLinePopFormat := true; SMTP.SASLMechanisms.Add.SASL := IdSASLXOAUTH; |
AW: Indy und OAuth / Microsoft365
So, ich habe nochmal nachgesehen.
Es wird nur der tatsächliche Token verwendet, nicht das komplette JSON, das war nur eine Logausgabe von dem Teil, der den Token abfragt. Habe es auch mit der hier erwähnten Variante von TIdSASLXOAuth versucht. Eine TIdSASLXOAuth2 ist im Fork von Remy Lebeau enthalten, diesen Fork habe ich verwendet. Als Scope verwendet die Bibliothek (sgcWebSockets) 'https://graph.microsoft.com/.default' (Token Abfrage für eine Service App). Bekomme immer noch exakt den gleichen Fehler. Kann ich in Microsoft365 noch etwas entscheidendes übersehen haben ? Kann man in Microsft365 irgendwo fehlgeschlagene Anmeldeversuche anzeigen lassen ? Habe jetzt unter ![]() Mal schauen ob da dann etwas angezeigt wird. Hat jemand einen Source zur Abfrage des Tokens mit "Bordmitteln" oder eine Empfehlung für eine andere Bibliothek dazu mit der es schon mal funktioniert hat ? Eventuell stimmt ja was bei der Tokenabfrage nicht. Habe es mit ![]() schon nicht. Scheint als ob da die tenant-id nicht berücksichtigt wird. Vielen Dank für die Antworten bisher. |
AW: Indy und OAuth / Microsoft365
und mit Scopes:
Delphi-Quellcode:
https://outlook.office.com/SMTP.Send offline_access openid email profile kann sein das das für dich zu viele sind, die verwende ich |
AW: Indy und OAuth / Microsoft365
Hilft dir das hier weiter?
![]() unter anderem: Zitat:
|
AW: Indy und OAuth / Microsoft365
So Leute,
habe es hinbekommen (ChatGPT sei Dank). Es lag an der Tokenabfrage. Mit ChatGPT habe ich folgende Funktion zur Tokenabfrage für eine "App-Registrierung" erstellt:
Delphi-Quellcode:
Mit dem Token der damit zurück kommt, klappt der Mailversand mit dem Source oben im Thread.uses System.Net.HttpClient, System.Net.URLClient, System.SysUtils, System.JSON, System.Classes, System.NetEncoding; function TForm1.GetAccessToken: string; var HttpClient: THTTPClient; AccessTokenURL, ClientID, ClientSecret, Scope: string; Response: IHTTPResponse; begin // Set your application-specific values AccessTokenURL := 'https://login.microsoftonline.com/<YOUR-TENANT-ID>/oauth2/v2.0/token'; ClientID := '<ANWENDUNGS-ID>'; ClientSecret := '<YOUR-CLIENT-SECRET>'; Scope := 'https://outlook.office365.com/.default'; // Scope for Microsoft 365 API // Create and configure the HTTP client HttpClient := THTTPClient.Create; try // Prepare the request parameters HttpClient.ContentType := 'application/x-www-form-urlencoded'; HttpClient.Accept := 'application/json'; // Create the request body var RequestData := 'grant_type=client_credentials' + '&client_id=' + TNetEncoding.URL.Encode(ClientID) + '&client_secret=' + TNetEncoding.URL.Encode(ClientSecret) + '&scope=' + TNetEncoding.URL.Encode(Scope); // Send the POST request to obtain an access token Response := HttpClient.Post(AccessTokenURL, TStringStream.Create(RequestData)); // Check for a successful response if Response.StatusCode = 200 then begin // Parse the JSON response to extract the access token // You may use a JSON library or implement your own parsing logic // Example: Extract the access token assuming JSON response format // You should add appropriate error handling and validation var AccessToken := ExtractAccessToken(Response.ContentAsString); // Return the access token Result := AccessToken; end else begin // Handle the case where the request fails (e.g., non-200 status code) // You should implement error handling according to your requirements Result := ''; end; finally HttpClient.Free; end; end; // Implement a function to extract the access token from the JSON response function TForm1.ExtractAccessToken(const JsonResponse: string): string; var JsonValue: TJSONValue; begin // Parse the JSON response to extract the access token // You may use a JSON library or implement your own parsing logic // Example (assuming JSON response format): // Search for the "access_token" key and extract its value // You should add appropriate error handling and validation // For demonstration purposes, we'll use Delphi's built-in JSON parser. JsonValue := TJSONObject.ParseJSONValue(JsonResponse); try if (JsonValue is TJSONObject) then begin Result := (JsonValue as TJSONObject).GetValue('access_token').Value; end else begin // Handle the case where JSON parsing fails Result := ''; end; finally JsonValue.Free; end; end; Vielen Dank für Eure Hilfe. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:02 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz