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.
- Der Benutzer öffnet Ihre Login-URL. Ihre Anwendung erstellt eine AuthnRequest und leitet den Browser zum IdP weiter.
- Der IdP meldet den Benutzer an, mit welchen Passwort-, MFA- oder Conditional-Access-Regeln das Unternehmen auch immer festgelegt hat.
- Der IdP antwortet mit einer signierten SAMLResponse, und der Browser sendet sie per POST an Ihre Assertion Consumer Service (ACS)-URL.
- 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.
- Beschreiben Sie Ihre Anwendung. Setzen Sie
EntityID, den eindeutigen Namen Ihrer Anwendung (meist ihre Metadaten-URL), undAssertionConsumerServiceURL, die https-URL, an der die Antwort eintrifft. - Beschreiben Sie den Identity Provider. Rufen Sie
LoadIdPMetadatamit dem Metadatendokument des IdP auf. Es liest die Entity ID des IdP, dessen Anmelde-URL und Binding sowie sämtliche Signaturzertifikate. Ohne Metadaten setzen SieIdPEntityID,IdPSSOURLundIdPCertificatesvon Hand. - Senden Sie die Anfrage.
GetAuthnRequestRedirectURLliefert die URL, zu der der Browser weitergeleitet wird. Für einen IdP, der nur das HTTP-POST-Binding anbietet, liefertGetAuthnRequestPostFormstattdessen eine Seite, die die Anfrage per POST sendet. - Merken Sie sich die Request-ID. Beide Methoden liefern die ID der neuen AuthnRequest. Speichern Sie sie auf dem Server, indiziert nach einem zufälligen RelayState oder dem Session-Cookie, und entfernen Sie sie, sobald die Antwort eintrifft, sodass jede Anfrage nur einmal beantwortet werden kann.
- Verarbeiten Sie die Antwort. Rufen Sie an der ACS-URL
ProcessResponsemit der übermittelten SAMLResponse, dem RelayState und der gespeicherten Request-ID auf. Liefert sieTrue, enthält einTsgcSAMLResultdieNameID, denSessionIndexund jedes vom IdP gesendete Attribut. Liefert sieFalse, nenntErrorMessageden Grund, undOnSAMLErrorwird ausgelöst.
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.
- Microsoft Entra ID. Enterprise applications, New application, Create your own application (non-gallery). Wählen Sie unter Single sign-on SAML, und laden Sie dann die SP-Metadaten hoch oder füllen Sie Identifier (Entity ID) und Reply URL aus. Weisen Sie Benutzer oder Gruppen zu, und laden Sie die unter SAML Certificates angezeigte App Federation Metadata Url.
- Okta. Applications, Create App Integration, SAML 2.0. Single sign-on URL ist Ihre ACS-URL, mit aktiviertem “Use this for Recipient URL and Destination URL”, und Audience URI ist Ihre Entity ID. Fügen Sie attribute statements wie email, firstName und lastName hinzu, weisen Sie Personen oder Gruppen zu, und laden Sie die Metadata URL aus dem Sign-On-Tab.
- AD FS. Fügen Sie einen claims aware Relying Party Trust hinzu und importieren Sie die SP-Metadaten. AD FS akzeptiert nur https-Endpunkte. Fügen Sie Claim Rules hinzu, die eine Name ID senden, zum Beispiel E-Mail-Addresses gesendet als E-Mail Address, dann E-Mail Address transformiert in Name ID. Die IdP-Metadaten liegen unter
https://<adfs-host>/FederationMetadata/2007-06/FederationMetadata.xml. - Google Workspace. Admin console, Apps, Web and mobile apps, Add custom SAML app. Laden Sie die IdP-Metadaten herunter, geben Sie Ihre ACS-URL und Entity ID ein, wählen Sie die Name ID (zum Beispiel die primäre E-Mail-Adresse) und schalten Sie die App für Ihre Benutzer ein.
- Keycloak. Erstellen Sie einen SAML client, dessen Client ID Ihre Entity ID ist, oder importieren Sie die SP-Metadaten. Keycloak signiert standardmäßig das gesamte Dokument, aktivieren Sie daher zusätzlich Sign assertions. Wenn Client signature required aktiviert ist, setzen Sie
SignAuthnRequests,SPCertificateundSPPrivateKey. Die IdP-Metadaten liegen unterhttps://<host>/realms/<realm>/protocol/saml/descriptor.
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:
- Die Signatur, gegen das IdP-Zertifikat. Die Antwort wird nur mit den Zertifikaten in
IdPCertificatesverifiziert. Einem in die Nachricht eingebetteten Zertifikat wird nie vertraut, weil ein Angreifer ebenfalls eines einbetten kann. MitWantAssertionsSigned, dem Standardwert, muss die Assertion ihre eigene Signatur tragen. - Schutz vor Signature Wrapping. Die Signatur muss auf ein Element verweisen, dessen ID im Dokument eindeutig ist, und nach der Verifikation wird nur das signierte Element gelesen. Eine unsignierte Assertion, die neben die signierte geschmuggelt wurde, wird nie betrachtet.
- Genau eine Assertion. Die Antwort muss genau eine Assertion enthalten, direkt unter der Response.
- Audience und Recipient. Die Audience muss Ihre
EntityIDsein und der Recipient IhreAssertionConsumerServiceURL, sodass eine für eine andere Anwendung ausgestellte Assertion abgelehnt wird. - Das Zeitfenster. NotBefore und NotOnOrAfter werden gegen UTC geprüft, mit einer Toleranz von
ClockSkewSekunden, standardmäßig zwei Minuten.MaxAssertionAgekann zusätzlich begrenzen, wie alt eine Assertion sein darf. - InResponseTo. Die Antwort muss die von Ihnen gespeicherte Request-ID beantworten. Unaufgeforderte, vom IdP initiierte Antworten werden abgelehnt, sofern Sie nicht
AllowIdPInitiatedsetzen. - Replay-Cache. Die ID jeder akzeptierten Assertion wird bis zu ihrem Ablauf aufbewahrt, sodass dieselbe Antwort ein zweites Mal gesendet abgelehnt wird. Der Cache ist thread-sicher und liegt im Speicher. Teilen sich mehrere Server die Anmeldung, überschreiben Sie
DoAddToReplayCache, um die IDs in einem gemeinsamen Speicher abzulegen. - SHA-1 standardmäßig deaktiviert. RSA-SHA1-Signaturen und SHA-1-Digests werden abgelehnt, sofern Sie nicht
AllowSHA1für einen IdP setzen, der sie noch benötigt.
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:
- 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.
- Klicken Sie auf Load IdP metadata. Die Standardquelle ist die Metadaten-URL von mocksaml.com.
- Klicken Sie auf Start, dann auf Open Browser, und folgen Sie dem Anmeldelink.
- Geben Sie auf mocksaml.com einen beliebigen Benutzernamen der Domain example.com und ein beliebiges Passwort ein.
- 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
- Keine verschlüsselten Assertions. Eine Antwort mit einer EncryptedAssertion oder einer verschlüsselten NameID wird abgelehnt. Lassen Sie die Assertion-Verschlüsselung im IdP deaktiviert. Die Assertion ist weiterhin signiert und wird über https übertragen.
- Kein Single Logout. SLO ist nicht implementiert.
SessionIndexwird zurückgegeben, damit Ihre Anwendung ihre eigene Sitzung beenden und ihren eigenen Logout aufbauen kann.
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
- Delphi-Login mit Passkeys, SAML SSO, LDAP und TOTP 2FA
- Delphi PKCE OAuth2
- Autorisierung mit PassKeys
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.
