SAML Single Sign-On in Delphi mit Entra ID, Okta und AD FS

· Komponenten
SAML Single Sign-On in Delphi mit Entra ID, Okta und AD FS

Früher oder später stellt ein großer Kunde die Frage: Können sich unsere Mitarbeiter mit ihrem Firmenkonto bei Ihrer Anwendung anmelden? Gemeint ist nicht noch ein weiterer Benutzername und ein weiteres Passwort. Gemeint ist die Anmeldung über Microsoft Entra ID, Okta oder AD FS, die sie ohnehin für alles andere verwenden, mit ihrer eigenen Passwortrichtlinie, ihrer eigenen Zwei-Faktor-Authentifizierung und einer einzigen Stelle, an der ein Konto abgeschaltet wird, sobald jemand das Unternehmen verlässt.

Die Antwort, die das Identitätsteam des Kunden erwartet, ist SAML 2.0. Im Überblick über die neuen Login-Komponenten bekam SAML nur einen Absatz. Dieser Beitrag zeigt den gesamten Ablauf: was TsgcSAMLServiceProvider leistet, den Code einer Login-Seite und eines Assertion Consumer Service, wie Sie Ihre Anwendung bei den gängigen Identity Providern registrieren, und wie Sie das alles noch heute testen, ohne irgendwo ein Konto zu besitzen.

So funktioniert die SAML-Anmeldung

Drei Parteien sind beteiligt. Ihre Anwendung ist der Service Provider (SP). Das Verzeichnis des Kunden ist der Identity Provider (IdP). Der Browser trägt die Nachrichten zwischen beiden, sodass Ihr Server und der IdP nie direkt miteinander sprechen.

  1. Der Benutzer öffnet Ihre Login-URL. Ihre Anwendung erstellt eine AuthnRequest und leitet den Browser zum IdP weiter.
  2. Der IdP meldet den Benutzer an, mit welchen Passwort-, MFA- oder Conditional-Access-Regeln das Unternehmen auch immer festgelegt hat.
  3. Der IdP antwortet mit einer signierten SAMLResponse, und der Browser sendet sie per POST an Ihre Assertion Consumer Service (ACS)-URL.
  4. Ihre Anwendung validiert die Antwort und erstellt eine eigene Sitzung für den darin genannten Benutzer.

Schritt vier ist der Punkt, an dem SAML-Implementierungen scheitern, und genau diesen Teil übernimmt die Komponente für Sie.

Der Service Provider, Schritt für Schritt

TsgcSAMLServiceProvider ist kein HTTP-Server. Er erstellt und prüft die SAML-Nachrichten, und Sie rufen ihn aus dem Request-Handler des Servers auf, den Ihre Anwendung bereits hat, zum Beispiel einem TsgcWebSocketHTTPServer oder einem TsgcHTTPServer.

Mit EntityID, AssertionConsumerServiceURL und LoadIdPMetadata einmalig beim Start erledigt, passen die Login-Seite und der ACS in einen einzigen Request-Handler:

uses
  sgcAuth_SAML_SP;

procedure TMyApp.OnCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  vRelayState, vRequestID: string;
  oResult: TsgcSAMLResult;
begin
  if ARequestInfo.Document = '/saml/login' then
  begin
    // 1. send the browser to the identity provider
    vRelayState := NewRelayState;
    AResponseInfo.Redirect(FSAML.GetAuthnRequestRedirectURL(vRelayState,
      vRequestID));
    // 2. keep the request id, the response must answer it
    AddPendingRequest(vRelayState, vRequestID);
  end
  else if (ARequestInfo.Document = '/saml/acs') and
    SameText(ARequestInfo.Command, 'POST') then
  begin
    // 3. the browser posts SAMLResponse and RelayState back
    vRelayState := ARequestInfo.Params.Values['RelayState'];
    vRequestID := TakePendingRequest(vRelayState);
    oResult := TsgcSAMLResult.Create;
    try
      if FSAML.ProcessResponse(ARequestInfo.Params.Values['SAMLResponse'],
        vRelayState, vRequestID, oResult) then
      begin
        // 4. signed in: create your own session for this user
        CreateUserSession(AResponseInfo, oResult.NameID, oResult.SessionIndex);
        AResponseInfo.Redirect('/');
      end
      else
        AResponseInfo.ResponseNo := 403; // log oResult.ErrorMessage
    finally
      oResult.Free;
    end;
  end;
end;

NewRelayState, AddPendingRequest, TakePendingRequest und CreateUserSession stehen für Ihren eigenen Code: eine GUID, eine thread-sichere Liste, indiziert nach RelayState, die jede Request-ID nur einmal ausgibt, und das Session-Cookie Ihrer Anwendung. Attribute kommen als Name=Value-Zeilen an, sodass oResult.Attributes.Values['email'] eines anhand seines Namens liest. Entra ID benennt sie mit Claim-URIs wie http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.

Der RelayState ist nicht durch die Signatur des IdP abgedeckt. Verwenden Sie ihn als Schlüssel, um Ihre eigene ausstehende Anfrage zu finden, niemals als URL, zu der Sie ungeprüft weiterleiten.

Ihre Anwendung beim Identity Provider registrieren

GetMetadata liefert die Metadaten des Service Providers: Ihre Entity ID und Ihre ACS-URL mit dem HTTP-POST-Binding. Stellen Sie sie unter einer URL wie /saml/metadata bereit, oder speichern Sie sie in einer Datei, und geben Sie sie dem IdP. Jeder Identity Provider fragt nach denselben zwei Werten, der Entity ID des SP und der ACS-URL, daher geht es in den folgenden Hinweisen vor allem darum, wo jede Konsole diese ablegt. Lassen Sie bei allen die Assertion-Verschlüsselung deaktiviert.

Unabhängig vom IdP ist der letzte Schritt derselbe: Übergeben Sie dessen Metadaten an LoadIdPMetadata. Beschreibt das Dokument mehrere Entitäten, wählt der zweite Parameter Ihre aus.

Was ProcessResponse prüft

Eine SAML-Antwort ist ein signiertes XML-Dokument, und die meisten bekannten SAML-Schwachstellen sind Wege, einen Service Provider dazu zu bringen, etwas anderes zu lesen als das, was signiert wurde. Eine Antwort wird nur akzeptiert, wenn jede dieser Prüfungen besteht:

Der Parser lehnt außerdem DOCTYPE-Deklarationen ab, sodass es keine externen Entitäten gibt, und er begrenzt die Größe und Verschachtelungstiefe des Dokuments. Der Issuer muss der von Ihnen konfigurierte IdP sein. Die erste fehlgeschlagene Prüfung stoppt die Validierung, und ihr Grund steht in ErrorMessage: protokollieren Sie ihn, und zeigen Sie dem Benutzer eine schlichte “Anmeldung fehlgeschlagen”-Seite.

Ohne Konto ausprobieren

Sie benötigen keinen Entra-ID-Tenant, um SAML in Aktion zu sehen. Mock SAML ist ein kostenloser Test-Identity-Provider unter mocksaml.com. Er akzeptiert jeden Service Provider und übernimmt Audience und ACS-URL aus der AuthnRequest, es gibt also nichts zu registrieren.

Die Demo Demos\26.Authentication\03.SAML_ServiceProvider ist ein vollständiger Service Provider auf einem TsgcWebSocketHTTPServer, mit den Endpunkten /login, /acs und /metadata unter http://localhost:8090:

  1. Erstellen Sie die Demo und halten Sie libcrypto-3.dll und libssl-3.dll neben der ausführbaren Datei. Sie liegen im Demo-Ordner, und OpenSSL verifiziert die RSA-Signaturen.
  2. Klicken Sie auf Load IdP metadata. Die Standardquelle ist die Metadaten-URL von mocksaml.com.
  3. Klicken Sie auf Start, dann auf Open Browser, und folgen Sie dem Anmeldelink.
  4. Geben Sie auf mocksaml.com einen beliebigen Benutzernamen der Domain example.com und ein beliebiges Passwort ein.
  5. Der Browser kehrt zum ACS zurück, und die Seite zeigt die NameID, den SessionIndex und die Attribute id, email, firstName und lastName.

Sobald das funktioniert, öffnen Sie http://localhost:8090/metadata, registrieren Sie es bei Ihrem echten IdP, laden Sie die IdP-Metadaten in der Demo und melden Sie sich erneut an. Führen Sie die Demo für AD FS zuerst mit SSL aus, da AD FS nur https akzeptiert.

Aktuelle Grenzen

Dokumentation

Wo Sie es bekommen

TsgcSAMLServiceProvider ist in den Editionen Enterprise und All-Access von sgcWebSockets für Delphi und C++ Builder enthalten, und dieselbe Komponente ist Teil von sgcWebSockets .NET. Wenn Sie nur Authentifizierung benötigen, enthält das sgcAuth-Paket sie zusammen mit den anderen Login-Komponenten. Die Unit heißt sgcAuth_SAML_SP, und in einer bestehenden Anwendung ändert sich nichts, bis Sie die Komponente auf ein Formular ziehen.

Weiterlesen

Video ansehen

Es gibt ein kurzes Video, “SAML single sign-on in Delphi with Entra ID, Okta and AD FS”, auf dem eSeGeCe-Kanal. Es zeigt den Code in der IDE und eine Live-Anmeldung mit der Demo gegen mocksaml.com.

Fragen, Feedback oder Hilfe bei der Anbindung Ihres Identity Providers? Kontaktieren Sie uns. Sie erhalten eine Antwort von den Leuten, die den Code geschrieben haben.