Inicio de sesión único SAML en Delphi con Entra ID, Okta y AD FS

· Componentes
Inicio de sesión único SAML en Delphi con Entra ID, Okta y AD FS

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.

  1. El usuario abre tu URL de inicio de sesión. Tu aplicación construye un AuthnRequest y redirige el navegador al IdP.
  2. El IdP inicia la sesión del usuario, con las reglas de contraseña, MFA o acceso condicional que tenga la empresa.
  3. El IdP responde con un SAMLResponse firmado, y el navegador lo envía mediante POST a tu URL de Assertion Consumer Service (ACS).
  4. 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.

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.

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:

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:

  1. 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.
  2. Haz clic en Load IdP metadata. El origen por defecto es la URL de metadatos de mocksaml.com.
  3. Haz clic en Start, luego en Open Browser, y sigue el enlace de inicio de sesión.
  4. En mocksaml.com, escribe cualquier nombre de usuario del dominio example.com y cualquier contraseña.
  5. 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

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

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.