Tarde o temprano un cliente grande hace la pregunta: ¿puede nuestro personal iniciar sesión en tu aplicación con su cuenta corporativa? No se refieren a otro nombre de usuario y contraseña más. Se refieren al inicio de sesión de Microsoft Entra ID, Okta o AD FS que ya usan para todo lo demás, con su propia política de contraseñas, su propio doble factor y un solo lugar donde desactivar una cuenta el día en que alguien se va.
La respuesta que espera su equipo de identidad es SAML 2.0. En la visión general de los nuevos componentes de inicio de sesión SAML ocupó un solo párrafo. Esta entrada es el flujo completo: qué hace TsgcSAMLServiceProvider, el código de una página de inicio de sesión y un Assertion Consumer Service, cómo registrar tu aplicación con los proveedores de identidad más comunes, y cómo probarlo todo hoy mismo sin tener cuenta en ningún sitio.
Cómo funciona el inicio de sesión SAML
Participan tres partes. Tu aplicación es el proveedor de servicios (SP). El directorio del cliente es el proveedor de identidad (IdP). El navegador transporta los mensajes entre ambos, de modo que tu servidor y el IdP nunca se comunican directamente.
- El usuario abre tu URL de inicio de sesión. Tu aplicación construye un AuthnRequest y redirige el navegador al IdP.
- El IdP inicia la sesión del usuario, con las reglas de contraseña, MFA o acceso condicional que tenga la empresa.
- El IdP responde con un SAMLResponse firmado, y el navegador lo envía mediante POST a tu URL de Assertion Consumer Service (ACS).
- Tu aplicación valida la respuesta y crea su propia sesión para el usuario que esta indica.
El paso cuatro es donde las implementaciones de SAML fallan, y es la parte que el componente hace por ti.
El proveedor de servicios, paso a paso
TsgcSAMLServiceProvider no es un servidor HTTP. Construye y comprueba los mensajes SAML, y lo llamas desde el manejador de peticiones del servidor que tu aplicación ya tiene, por ejemplo un TsgcWebSocketHTTPServer o un TsgcHTTPServer.
- Describe tu aplicación. Asigna
EntityID, el nombre único de tu aplicación (normalmente la URL de sus metadatos), yAssertionConsumerServiceURL, la URL https donde llega la respuesta. - Describe el proveedor de identidad. Llama a
LoadIdPMetadatacon el documento de metadatos del IdP. Lee el entity ID del IdP, su URL de inicio de sesión y su binding, y todos los certificados de firma. Sin metadatos, asignaIdPEntityID,IdPSSOURLeIdPCertificatesa mano. - Envía la solicitud.
GetAuthnRequestRedirectURLdevuelve la URL a la que redirigir el navegador. Para un IdP que solo ofrece el binding HTTP-POST,GetAuthnRequestPostFormdevuelve en su lugar una página que envía la solicitud mediante POST. - Guarda el id de la solicitud. Ambos métodos devuelven el id del nuevo AuthnRequest. Guárdalo en el servidor, indexado por un RelayState aleatorio o por la cookie de sesión, y elimínalo cuando llegue la respuesta, de modo que cada solicitud pueda responderse una sola vez.
- Procesa la respuesta. En la URL del ACS, llama a
ProcessResponsecon el SAMLResponse recibido, el RelayState y el id de solicitud guardado. Cuando devuelveTrue, unTsgcSAMLResultcontiene elNameID, elSessionIndexy todos los atributos que envió el IdP. Cuando devuelveFalse,ErrorMessageexplica el motivo y se disparaOnSAMLError.
Con EntityID, AssertionConsumerServiceURL y LoadIdPMetadata hechos una vez al arrancar, la página de inicio de sesión y el ACS caben en un solo manejador de peticiones:
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 y CreateUserSession representan tu propio código: un GUID, una lista thread safe indexada por RelayState que entrega cada id de solicitud una sola vez, y la cookie de sesión de tu aplicación. Los atributos llegan como líneas Name=Value, así que oResult.Attributes.Values['email'] lee uno por su nombre. Entra ID los nombra con claim URIs como http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.
El RelayState no está cubierto por la firma del IdP. Úsalo como clave para encontrar tu propia solicitud pendiente, nunca como una URL a la que rediriges sin comprobarla.
Registra tu aplicación en el proveedor de identidad
GetMetadata devuelve los metadatos del proveedor de servicios: tu entity ID y tu URL de ACS con el binding HTTP-POST. Sírvelos en una URL como /saml/metadata, o guárdalos en un archivo, y entrégaselos al IdP. Todos los proveedores de identidad piden los mismos dos valores, el entity ID del SP y la URL del ACS, así que las notas siguientes tratan sobre todo de dónde guarda cada consola esos datos. En todos ellos, deja desactivado el cifrado de aserciones.
- Microsoft Entra ID. Enterprise applications, New application, Create your own application (non-gallery). En Single sign-on elige SAML, y luego sube los metadatos del SP o rellena Identifier (Entity ID) y Reply URL. Asigna usuarios o grupos, y carga el App Federation Metadata Url que se muestra en SAML Certificates.
- Okta. Applications, Create App Integration, SAML 2.0. Single sign-on URL es tu URL de ACS, con “Use this for Recipient URL and Destination URL” marcado, y Audience URI es tu entity ID. Añade attribute statements como email, firstName y lastName, asigna personas o grupos, y carga el Metadata URL desde la pestaña Sign On.
- AD FS. Añade un claims aware Relying Party Trust e importa los metadatos del SP. AD FS solo acepta endpoints https. Añade claim rules que envíen un Name ID, por ejemplo E-Mail-Addresses enviado como E-Mail Address, y luego E-Mail Address transformado en Name ID. Los metadatos del IdP están en
https://<adfs-host>/FederationMetadata/2007-06/FederationMetadata.xml. - Google Workspace. Admin console, Apps, Web and mobile apps, Add custom SAML app. Descarga los metadatos del IdP, introduce tu URL de ACS y tu entity ID, elige el Name ID (por ejemplo el correo principal) y activa la aplicación para tus usuarios.
- Keycloak. Crea un SAML client cuyo Client ID sea tu entity ID, o importa los metadatos del SP. Keycloak firma todo el documento por defecto, así que activa también Sign assertions. Si Client signature required está activado, asigna
SignAuthnRequests,SPCertificateySPPrivateKey. Los metadatos del IdP están enhttps://<host>/realms/<realm>/protocol/saml/descriptor.
Sea cual sea el IdP, el último paso es el mismo: pasa sus metadatos a LoadIdPMetadata. Cuando el documento describe varias entidades, el segundo parámetro elige la tuya.
Qué comprueba ProcessResponse
Una respuesta SAML es un documento XML firmado, y la mayoría de las vulnerabilidades SAML conocidas son formas de hacer que un proveedor de servicios lea algo distinto de lo que se firmó. Una respuesta se acepta solo cuando pasan todas estas comprobaciones:
- La firma, contra el certificado del IdP. La respuesta se verifica solo con los certificados de
IdPCertificates. Un certificado incrustado en el mensaje nunca es de confianza, porque un atacante también puede incrustar uno. ConWantAssertionsSigned, el valor por defecto, la aserción debe llevar su propia firma. - Defensa contra signature wrapping. La firma debe hacer referencia a un elemento cuyo ID sea único en el documento, y después de la verificación solo se lee el elemento firmado. Una aserción sin firmar colada junto a la firmada nunca se examina.
- Una sola aserción. La respuesta debe contener exactamente una aserción, directamente bajo la respuesta.
- Audiencia y destinatario. La audiencia debe ser tu
EntityIDy el destinatario tuAssertionConsumerServiceURL, de modo que una aserción emitida para otra aplicación se rechaza. - La ventana de tiempo. NotBefore y NotOnOrAfter se comprueban contra UTC con una tolerancia de
ClockSkewsegundos, dos minutos por defecto.MaxAssertionAgetambién puede limitar la antigüedad máxima de una aserción. - InResponseTo. La respuesta debe responder al id de solicitud que guardaste. Las respuestas no solicitadas, iniciadas por el IdP, se rechazan a menos que asignes
AllowIdPInitiated. - Caché de reproducción. El ID de cada aserción aceptada se conserva hasta que expira, de modo que la misma respuesta enviada dos veces se rechaza. La caché es thread safe y reside en memoria. Cuando varios servidores comparten el inicio de sesión, sobrescribe
DoAddToReplayCachepara guardar los ID en un almacén compartido. - SHA-1 desactivado por defecto. Las firmas RSA-SHA1 y los digests SHA-1 se rechazan a menos que asignes
AllowSHA1para un IdP que todavía los necesite.
El analizador también rechaza las declaraciones DOCTYPE, de modo que no hay entidades externas, y limita el tamaño y la profundidad de anidamiento del documento. El emisor debe ser el IdP que configuraste. La primera comprobación que falla detiene la validación y su motivo queda en ErrorMessage: regístralo, y muestra al usuario una página sencilla de “error al iniciar sesión”.
Pruébalo sin tener cuenta
No necesitas un tenant de Entra ID para ver SAML en funcionamiento. Mock SAML es un proveedor de identidad de prueba gratuito en mocksaml.com. Acepta cualquier proveedor de servicios y toma la audiencia y la URL del ACS del AuthnRequest, así que no hay nada que registrar.
La demo Demos\26.Authentication\03.SAML_ServiceProvider es un proveedor de servicios completo sobre un TsgcWebSocketHTTPServer, con los endpoints /login, /acs y /metadata en http://localhost:8090:
- Compila la demo y mantén libcrypto-3.dll y libssl-3.dll junto al ejecutable. Están en la carpeta de la demo, y OpenSSL verifica las firmas RSA.
- Haz clic en Load IdP metadata. El origen por defecto es la URL de metadatos de mocksaml.com.
- Haz clic en Start, luego en Open Browser, y sigue el enlace de inicio de sesión.
- En mocksaml.com, escribe cualquier nombre de usuario del dominio example.com y cualquier contraseña.
- El navegador vuelve al ACS, y la página muestra el NameID, el SessionIndex y los atributos id, email, firstName y lastName.
Cuando eso funcione, abre http://localhost:8090/metadata, regístralo en tu IdP real, carga los metadatos del IdP en la demo e inicia sesión de nuevo. Para AD FS, ejecuta primero la demo con SSL, porque AD FS solo acepta https.
Límites actuales
- Sin aserciones cifradas. Una respuesta con un EncryptedAssertion o un NameID cifrado se rechaza. Deja el cifrado de aserciones desactivado en el IdP. La aserción sigue firmada y viaja por https.
- Sin Single Logout. SLO no está implementado. Se devuelve
SessionIndexpara que tu aplicación pueda terminar su propia sesión y construir su propio cierre de sesión.
Documentación
Dónde conseguirlo
TsgcSAMLServiceProvider está incluido en las ediciones Enterprise y All-Access de sgcWebSockets, para Delphi y C++ Builder, y el mismo componente forma parte de sgcWebSockets .NET. Si solo necesitas autenticación, el paquete sgcAuth lo incluye junto con los demás componentes de inicio de sesión. La unidad es sgcAuth_SAML_SP, y nada cambia en una aplicación existente hasta que colocas el componente en un formulario.
Sigue leyendo
- Inicio de sesión en Delphi con Passkeys, SAML SSO, LDAP y TOTP 2FA
- PKCE OAuth2 en Delphi
- Autorización mediante PassKeys
Míralo en vídeo
Hay un vídeo breve, “SAML single sign-on in Delphi with Entra ID, Okta and AD FS”, en el canal de eSeGeCe. Muestra el código en el IDE y un inicio de sesión en vivo con la demo contra mocksaml.com.
¿Preguntas, comentarios o ayuda para conectar tu proveedor de identidad? Ponte en contacto. Recibirás respuesta de las personas que escribieron el código.
