Prima o poi un grande cliente pone la domanda: il nostro personale può accedere alla vostra applicazione con l'account aziendale? Non intendono un altro nome utente e un'altra password. Intendono l'accesso a Microsoft Entra ID, Okta o AD FS che già usano per tutto il resto, con la propria politica delle password, il proprio secondo fattore e un unico punto da cui disattivare un account il giorno in cui qualcuno lascia l'azienda.
La risposta che il loro team identità si aspetta è SAML 2.0. Nella panoramica dei nuovi componenti di login SAML ha avuto un solo paragrafo. Questo articolo è l'intero flusso: cosa fa TsgcSAMLServiceProvider, il codice di una pagina di login e di un Assertion Consumer Service, come registrare la vostra applicazione presso gli identity provider più comuni, e come provare tutto questo già oggi senza un account da nessuna parte.
Come funziona l'accesso SAML
Sono coinvolte tre parti. La vostra applicazione è il service provider (SP). La directory del cliente è l'identity provider (IdP). Il browser porta i messaggi tra i due, quindi il vostro server e l'IdP non comunicano mai direttamente.
- L'utente apre l'URL di login. La vostra applicazione costruisce un AuthnRequest e reindirizza il browser verso l'IdP.
- L'IdP autentica l'utente, con qualsiasi regola di password, MFA o accesso condizionale che l'azienda abbia definito.
- L'IdP risponde con una SAMLResponse firmata, e il browser la invia tramite POST all'URL del vostro Assertion Consumer Service (ACS).
- La vostra applicazione convalida la risposta e crea una propria sessione per l'utente che questa indica.
Il passo quattro è il punto in cui le implementazioni SAML sbagliano, ed è la parte che il componente fa per voi.
Il service provider, passo dopo passo
TsgcSAMLServiceProvider non è un server HTTP. Costruisce e verifica i messaggi SAML, e lo chiamate dal request handler del server che la vostra applicazione già possiede, ad esempio un TsgcWebSocketHTTPServer o un TsgcHTTPServer.
- Descrivete la vostra applicazione. Impostate
EntityID, il nome univoco della vostra applicazione (di solito il suo URL dei metadati), eAssertionConsumerServiceURL, l'URL https a cui arriva la risposta. - Descrivete l'identity provider. Chiamate
LoadIdPMetadatacon il documento dei metadati dell'IdP. Legge l'entity ID dell'IdP, il suo URL di login e il binding, e tutti i certificati di firma. Senza metadati, impostate manualmenteIdPEntityID,IdPSSOURLeIdPCertificates. - Inviate la richiesta.
GetAuthnRequestRedirectURLrestituisce l'URL a cui reindirizzare il browser. Per un IdP che offre solo il binding HTTP-POST,GetAuthnRequestPostFormrestituisce invece una pagina che invia la richiesta tramite POST. - Conservate l'id della richiesta. Entrambi i metodi restituiscono l'id della nuova AuthnRequest. Conservatelo sul server, indicizzato per un RelayState casuale o per il cookie di sessione, e rimuovetelo quando arriva la risposta, in modo che ogni richiesta possa ricevere risposta una sola volta.
- Elaborate la risposta. All'URL dell'ACS, chiamate
ProcessResponsecon la SAMLResponse ricevuta, il RelayState e l'id di richiesta conservato. Quando restituisceTrue, unTsgcSAMLResultcontiene ilNameID, ilSessionIndexe ogni attributo inviato dall'IdP. Quando restituisceFalse,ErrorMessagene spiega il motivo e si attivaOnSAMLError.
Con EntityID, AssertionConsumerServiceURL e LoadIdPMetadata impostati una volta all'avvio, la pagina di login e l'ACS stanno in un unico 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 e CreateUserSession rappresentano il vostro codice: un GUID, una lista thread safe indicizzata per RelayState che consegna ogni id di richiesta una sola volta, e il cookie di sessione della vostra applicazione. Gli attributi arrivano come righe Name=Value, quindi oResult.Attributes.Values['email'] ne legge uno per nome. Entra ID li nomina con claim URI come http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.
Il RelayState non è coperto dalla firma dell'IdP. Usatelo come chiave per trovare la vostra richiesta in sospeso, mai come un URL verso cui reindirizzare senza controllarlo.
Registrare la vostra applicazione presso l'identity provider
GetMetadata restituisce i metadati del service provider: il vostro entity ID e il vostro URL ACS con il binding HTTP-POST. Pubblicateli a un URL come /saml/metadata, oppure salvateli in un file, e consegnateli all'IdP. Ogni identity provider chiede gli stessi due valori, l'entity ID dell'SP e l'URL dell'ACS, quindi le note seguenti riguardano soprattutto dove ogni console li conserva. In tutti, lasciate disattivata la cifratura delle asserzioni.
- Microsoft Entra ID. Enterprise applications, New application, Create your own application (non-gallery). In Single sign-on scegliete SAML, poi caricate i metadati dell'SP o compilate Identifier (Entity ID) e Reply URL. Assegnate utenti o gruppi, e caricate l'App Federation Metadata Url mostrato in SAML Certificates.
- Okta. Applications, Create App Integration, SAML 2.0. Single sign-on URL è il vostro URL ACS, con “Use this for Recipient URL and Destination URL” selezionato, e Audience URI è il vostro entity ID. Aggiungete attribute statements come email, firstName e lastName, assegnate persone o gruppi, e caricate il Metadata URL dalla scheda Sign On.
- AD FS. Aggiungete un claims aware Relying Party Trust e importate i metadati dell'SP. AD FS accetta solo endpoint https. Aggiungete claim rules che inviino un Name ID, ad esempio E-Mail-Addresses inviato come E-Mail Address, poi E-Mail Address trasformato in Name ID. I metadati dell'IdP si trovano su
https://<adfs-host>/FederationMetadata/2007-06/FederationMetadata.xml. - Google Workspace. Admin console, Apps, Web and mobile apps, Add custom SAML app. Scaricate i metadati dell'IdP, inserite il vostro URL ACS e l'entity ID, scegliete il Name ID (ad esempio l'email principale) e attivate l'app per i vostri utenti.
- Keycloak. Create un SAML client il cui Client ID sia il vostro entity ID, oppure importate i metadati dell'SP. Keycloak firma l'intero documento per impostazione predefinita, quindi attivate anche Sign assertions. Se Client signature required è attivo, impostate
SignAuthnRequests,SPCertificateeSPPrivateKey. I metadati dell'IdP si trovano suhttps://<host>/realms/<realm>/protocol/saml/descriptor.
Qualunque sia l'IdP, l'ultimo passo è lo stesso: passate i suoi metadati a LoadIdPMetadata. Quando il documento descrive più entità, il secondo parametro sceglie la vostra.
Cosa verifica ProcessResponse
Una risposta SAML è un documento XML firmato, e la maggior parte delle vulnerabilità SAML note sono modi per far leggere a un service provider qualcosa di diverso da ciò che è stato firmato. Una risposta viene accettata solo quando tutti questi controlli passano:
- La firma, contro il certificato dell'IdP. La risposta viene verificata solo con i certificati in
IdPCertificates. Un certificato incorporato nel messaggio non è mai considerato attendibile, perché un attaccante può incorporarne uno a sua volta. ConWantAssertionsSigned, il valore predefinito, l'asserzione deve avere una propria firma. - Difesa dal signature wrapping. La firma deve fare riferimento a un elemento il cui ID sia univoco nel documento, e dopo la verifica viene letto solo l'elemento firmato. Un'asserzione non firmata inserita accanto a quella firmata non viene mai considerata.
- Una sola asserzione. La risposta deve contenere esattamente un'asserzione, direttamente sotto la response.
- Audience e recipient. L'audience deve essere il vostro
EntityIDe il recipient il vostroAssertionConsumerServiceURL, in modo che un'asserzione emessa per un'altra applicazione venga rifiutata. - La finestra temporale. NotBefore e NotOnOrAfter vengono verificati rispetto all'UTC con una tolleranza di
ClockSkewsecondi, due minuti per impostazione predefinita.MaxAssertionAgepuò anche limitare quanto può essere vecchia un'asserzione. - InResponseTo. La risposta deve rispondere all'id di richiesta che avete conservato. Le risposte non richieste, avviate dall'IdP, vengono rifiutate a meno che non impostiate
AllowIdPInitiated. - Cache anti-replay. L'ID di ogni asserzione accettata viene conservato fino alla scadenza, quindi la stessa risposta inviata due volte viene rifiutata. La cache è thread safe e risiede in memoria. Quando più server condividono il login, sovrascrivete
DoAddToReplayCacheper conservare gli ID in un archivio condiviso. - SHA-1 disattivato per impostazione predefinita. Le firme RSA-SHA1 e i digest SHA-1 vengono rifiutati a meno che non impostiate
AllowSHA1per un IdP che ne abbia ancora bisogno.
Il parser rifiuta anche le dichiarazioni DOCTYPE, quindi non ci sono entità esterne, e limita dimensione e profondità di nidificazione del documento. L'issuer deve essere l'IdP configurato. Il primo controllo fallito ferma la convalida e il suo motivo si trova in ErrorMessage: registratelo, e mostrate all'utente una semplice pagina “accesso non riuscito”.
Provalo senza un account
Non serve un tenant Entra ID per vedere SAML in funzione. Mock SAML è un identity provider di test gratuito su mocksaml.com. Accetta qualsiasi service provider e prende audience e URL ACS dall'AuthnRequest, quindi non c'è nulla da registrare.
La demo Demos\26.Authentication\03.SAML_ServiceProvider è un service provider completo su un TsgcWebSocketHTTPServer, con endpoint /login, /acs e /metadata su http://localhost:8090:
- Compilate la demo e tenete libcrypto-3.dll e libssl-3.dll accanto all'eseguibile. Sono nella cartella della demo, e OpenSSL verifica le firme RSA.
- Fate clic su Load IdP metadata. La fonte predefinita è l'URL dei metadati di mocksaml.com.
- Fate clic su Start, poi su Open Browser, e seguite il link di accesso.
- Su mocksaml.com, digitate un nome utente qualsiasi sul dominio example.com e una password qualsiasi.
- Il browser torna all'ACS, e la pagina mostra il NameID, il SessionIndex e gli attributi id, email, firstName e lastName.
Quando funziona, aprite http://localhost:8090/metadata, registratelo presso il vostro IdP reale, caricate i metadati dell'IdP nella demo e accedete di nuovo. Per AD FS, eseguite prima la demo con SSL, perché AD FS accetta solo https.
Limiti attuali
- Nessuna asserzione cifrata. Una risposta con un EncryptedAssertion o un NameID cifrato viene rifiutata. Lasciate disattivata la cifratura delle asserzioni nell'IdP. L'asserzione resta comunque firmata e viaggia su https.
- Nessun Single Logout. SLO non è implementato.
SessionIndexviene restituito in modo che la vostra applicazione possa terminare la propria sessione e costruire il proprio logout.
Documentazione
Dove trovarlo
TsgcSAMLServiceProvider è incluso nelle edizioni Enterprise e All-Access di sgcWebSockets, per Delphi e C++ Builder, e lo stesso componente fa parte di sgcWebSockets .NET. Se vi serve solo l'autenticazione, il pacchetto sgcAuth lo offre insieme agli altri componenti di login. L'unit è sgcAuth_SAML_SP, e nulla cambia in un'applicazione esistente finché non posizionate il componente su una form.
Continua a leggere
- Login Delphi con Passkey, SAML SSO, LDAP e TOTP 2FA
- PKCE OAuth2 in Delphi
- Autorizzazione tramite PassKey
Guardalo in video
C'è un breve video, “SAML single sign-on in Delphi with Entra ID, Okta and AD FS”, sul canale eSeGeCe. Mostra il codice nell'IDE e un accesso dal vivo con la demo contro mocksaml.com.
Domande, feedback o aiuto per collegare il vostro identity provider? Contattateci. Riceverete una risposta dalle persone che hanno scritto il codice.
