Jednokrotne logowanie SAML w Delphi z Entra ID, Okta i AD FS

· Komponenty
Jednokrotne logowanie SAML w Delphi z Entra ID, Okta i AD FS

Prędzej czy później duży klient zadaje pytanie: czy nasi pracownicy mogą logować się do waszej aplikacji za pomocą konta firmowego? Nie chodzi im o kolejną nazwę użytkownika i hasło. Chodzi im o logowanie Microsoft Entra ID, Okta lub AD FS, którego już używają do wszystkiego innego, z własną polityką haseł, własnym drugim składnikiem uwierzytelniania i jednym miejscem, w którym można wyłączyć konto w dniu odejścia pracownika.

Odpowiedzią, jakiej oczekuje ich zespół ds. tożsamości, jest SAML 2.0. W przeglądzie nowych komponentów logowania SAML zajął jeden akapit. Ten wpis to pełny przepływ: co robi TsgcSAMLServiceProvider, kod strony logowania i Assertion Consumer Service, jak zarejestrować aplikację u popularnych dostawców tożsamości oraz jak to wszystko przetestować już dziś bez konta gdziekolwiek.

Jak działa logowanie SAML

Biorą w nim udział trzy strony. Twoja aplikacja jest service providerem (SP). Katalog klienta jest identity providerem (IdP). Przeglądarka przenosi komunikaty między nimi, więc twój serwer i IdP nigdy nie rozmawiają ze sobą bezpośrednio.

  1. Użytkownik otwiera twój URL logowania. Twoja aplikacja buduje AuthnRequest i przekierowuje przeglądarkę do IdP.
  2. IdP loguje użytkownika, zgodnie z regułami haseł, MFA lub dostępu warunkowego ustalonymi przez firmę.
  3. IdP odpowiada podpisanym SAMLResponse, a przeglądarka wysyła je metodą POST do URL twojego Assertion Consumer Service (ACS).
  4. Twoja aplikacja waliduje odpowiedź i tworzy własną sesję dla użytkownika, którego ta odpowiedź wskazuje.

Krok czwarty to miejsce, w którym implementacje SAML popełniają błędy, i jest to dokładnie ta część, którą komponent wykonuje za ciebie.

Service provider, krok po kroku

TsgcSAMLServiceProvider nie jest serwerem HTTP. Buduje i sprawdza komunikaty SAML, a ty wywołujesz go z handlera żądań serwera, który twoja aplikacja już posiada, na przykład TsgcWebSocketHTTPServer lub TsgcHTTPServer.

Gdy EntityID, AssertionConsumerServiceURL i LoadIdPMetadata są ustawione raz przy starcie, strona logowania i ACS mieszczą się w jednym handlerze żądań:

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 i CreateUserSession reprezentują twój własny kod: GUID, thread safe listę indeksowaną RelayState, która wydaje każde id żądania tylko raz, oraz ciasteczko sesji twojej aplikacji. Atrybuty przychodzą jako linie Name=Value, więc oResult.Attributes.Values['email'] odczytuje jeden po nazwie. Entra ID nazywa je claim URI, takimi jak http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.

RelayState nie jest objęty podpisem IdP. Używaj go jako klucza do odnalezienia własnego oczekującego żądania, nigdy jako URL, do którego przekierowujesz bez sprawdzenia.

Rejestracja aplikacji u dostawcy tożsamości

GetMetadata zwraca metadane service providera: twój entity ID i twój URL ACS z bindingiem HTTP-POST. Udostępnij je pod URL takim jak /saml/metadata, lub zapisz je w pliku, i przekaż IdP. Każdy dostawca tożsamości prosi o te same dwie wartości, entity ID SP i URL ACS, więc poniższe notatki dotyczą głównie tego, gdzie każda konsola je przechowuje. We wszystkich pozostaw wyłączone szyfrowanie asercji.

Niezależnie od IdP, ostatni krok jest taki sam: przekaż jego metadane do LoadIdPMetadata. Gdy dokument opisuje kilka jednostek, drugi parametr wybiera twoją.

Co sprawdza ProcessResponse

Odpowiedź SAML jest podpisanym dokumentem XML, a większość znanych podatności SAML to sposoby na to, by service provider odczytał coś innego niż to, co zostało podpisane. Odpowiedź jest akceptowana tylko wtedy, gdy przejdą wszystkie poniższe kontrole:

Parser odrzuca również deklaracje DOCTYPE, więc nie ma zewnętrznych encji, i ogranicza rozmiar oraz głębokość zagnieżdżenia dokumentu. Issuer musi być IdP, który skonfigurowałeś. Pierwsza nieudana kontrola zatrzymuje walidację, a jej powód znajduje się w ErrorMessage: zapisz go w logu i pokaż użytkownikowi prostą stronę “logowanie nieudane”.

Wypróbuj bez konta

Nie potrzebujesz tenanta Entra ID, żeby zobaczyć SAML w działaniu. Mock SAML to darmowy testowy identity provider pod adresem mocksaml.com. Akceptuje dowolnego service providera i pobiera audience oraz URL ACS z AuthnRequest, więc nie ma nic do rejestrowania.

Demo Demos\26.Authentication\03.SAML_ServiceProvider to kompletny service provider na TsgcWebSocketHTTPServer, z endpointami /login, /acs i /metadata pod http://localhost:8090:

  1. Skompiluj demo i trzymaj libcrypto-3.dll oraz libssl-3.dll obok pliku wykonywalnego. Znajdują się w folderze demo, a OpenSSL weryfikuje podpisy RSA.
  2. Kliknij Load IdP metadata. Domyślnym źródłem jest URL metadanych mocksaml.com.
  3. Kliknij Start, potem Open Browser, i podążaj za linkiem logowania.
  4. Na mocksaml.com wpisz dowolną nazwę użytkownika z domeny example.com i dowolne hasło.
  5. Przeglądarka wraca do ACS, a strona pokazuje NameID, SessionIndex oraz atrybuty id, email, firstName i lastName.

Gdy to zadziała, otwórz http://localhost:8090/metadata, zarejestruj go u swojego prawdziwego IdP, wczytaj metadane IdP w demo i zaloguj się ponownie. W przypadku AD FS uruchom najpierw demo z SSL, ponieważ AD FS akceptuje wyłącznie https.

Obecne ograniczenia

Dokumentacja

Gdzie go zdobyć

TsgcSAMLServiceProvider jest zawarty w edycjach Enterprise i All-Access sgcWebSockets, dla Delphi i C++ Builder, a ten sam komponent jest częścią sgcWebSockets .NET. Jeśli potrzebujesz wyłącznie uwierzytelniania, pakiet sgcAuth zawiera go razem z pozostałymi komponentami logowania. Jednostką jest sgcAuth_SAML_SP, a w istniejącej aplikacji nic się nie zmienia, dopóki nie umieścisz komponentu na formularzu.

Czytaj dalej

Zobacz wideo

Na kanale eSeGeCe dostępne jest krótkie wideo, “SAML single sign-on in Delphi with Entra ID, Okta and AD FS”. Pokazuje kod w IDE oraz logowanie na żywo z użyciem demo względem mocksaml.com.

Masz pytania, uwagi lub potrzebujesz pomocy w podłączeniu swojego dostawcy tożsamości? Skontaktuj się z nami. Otrzymasz odpowiedź od osób, które napisały ten kod.