El primer cliente grande que firme un contrato pedirá inicio de sesión único con su proveedor de identidad, y muy a menudo eso significa SAML 2.0. El estándar es de 2005, mueve XML mediante redirecciones del navegador y envíos de formularios, y sus mensajes de error van de lo vago a lo mudo. La mayoría de los bugs de SAML, incluidos agujeros de seguridad graves, vienen de no saber exactamente qué mensaje va adónde y qué debe comprobar el proveedor de servicios.
Este artículo recorre el flujo iniciado por el SP mensaje a mensaje con un diagrama de secuencia, y después la variante iniciada por el IdP y sus riesgos, la anatomía de una respuesta SAML, la lista de comprobaciones para tu app, los metadatos y la rotación de certificados, y cómo se compara SAML con OpenID Connect.
Las tres partes
| Parte | En términos de SAML | Ejemplos |
|---|---|---|
| El navegador del usuario | Agente de usuario | Cualquier navegador |
| Tu aplicación | Proveedor de servicios (SP) | Tu app SaaS |
| El sistema de inicio de sesión del cliente | Proveedor de identidad (IdP) | Microsoft Entra ID, Okta, Google Workspace, AD FS |
En el perfil habitual Web Browser SSO, el SP y el IdP nunca hablan directamente entre sí. Cada mensaje viaja a través del navegador, como una redirección o un formulario que se envía solo. Lo que comparten es configuración, intercambiada una vez como metadatos: el entity ID de cada lado, la URL de inicio de sesión del IdP, la URL del Assertion Consumer Service (ACS) del SP y el certificado de firma del IdP.
El flujo iniciado por el SP, paso a paso
El usuario empieza en tu app:
- El usuario abre
https://app.example.com/dashboardsin sesión. - Tu app crea un
AuthnRequestcon un ID único, su entity ID y su URL ACS, guarda el ID en el estado del inicio de sesión pendiente y redirige el navegador al IdP. Con el binding HTTP-Redirect, la solicitud viaja en la query string comoSAMLRequest(comprimida con deflate y codificada en base64), junto a un valorRelayStateque recuerda adónde quería ir el usuario. - El IdP autentica al usuario: contraseña y MFA, o una sesión del IdP ya existente, en cuyo caso el usuario no ve nada.
- El IdP devuelve una página HTML con un formulario que envía por POST una
SAMLResponsefirmada y el mismoRelayStatea tu URL ACS. Es el binding HTTP-POST, y un pequeño script envía el formulario automáticamente. - Tu app valida la respuesta, crea su propia cookie de sesión y redirige al usuario a la página guardada en
RelayState.
A partir de ahí, la sesión propia de tu app decide quién es el usuario. SAML solo vuelve a intervenir cuando esa sesión caduca.
La especificación de bindings limita RelayState a 80 bytes, así que guarda la URL de retorno real en el servidor y envía una clave corta.
Qué contiene una respuesta SAML
Recortada a las partes que lee tu app, una respuesta tiene este aspecto:
<samlp:Response ID="_r81c" InResponseTo="_req42" Destination="https://app.example.com/saml/acs">
<saml:Assertion ID="_a77e">
<saml:Issuer>https://idp.customer.com/entity</saml:Issuer>
<ds:Signature>…</ds:Signature>
<saml:Subject>
<saml:NameID>ana@customer.com</saml:NameID>
<saml:SubjectConfirmationData InResponseTo="_req42"
Recipient="https://app.example.com/saml/acs" NotOnOrAfter="2026-11-02T09:05:00Z"/>
</saml:Subject>
<saml:Conditions NotBefore="2026-11-02T08:59:30Z" NotOnOrAfter="2026-11-02T09:05:00Z">
<saml:AudienceRestriction><saml:Audience>https://app.example.com</saml:Audience></saml:AudienceRestriction>
</saml:Conditions>
<saml:AttributeStatement>…email, name, groups…</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
La aserción es la declaración firmada: quién es el usuario (NameID), quién lo afirma (Issuer), para quién (Audience), durante cuánto tiempo y en respuesta a qué solicitud (InResponseTo).
La lista de comprobaciones
Tu endpoint ACS es donde la seguridad de SAML funciona o falla. Debe:
- Verificar la firma con el certificado de los metadatos del IdP, nunca con un certificado incrustado en el propio mensaje.
- Leer solo lo que cubre la firma. Los ataques de XML signature wrapping insertan una segunda aserción sin firmar y esperan que el código verifique un elemento y lea otro. Unos investigadores encontraron este fallo en 11 de 14 frameworks SAML importantes en 2012, y sigue volviendo: ruby-saml corrigió en 2024 una omisión de firma con CVSS 10.0 (CVE-2024-45409) y en 2025 dos omisiones por diferencias entre parsers (CVE-2025-25291 y CVE-2025-25292).
- Comprobar
Issuercontra el entity ID del IdP de este cliente. - Comprobar
Audiencecontra tu propio entity ID. - Comprobar
DestinationyRecipientcontra tu URL ACS. - Comprobar
NotBeforeyNotOnOrAftercon una tolerancia de uno o dos minutos por el desfase de reloj. - Hacer coincidir
InResponseTocon un ID deAuthnRequestque emitiste para este navegador y que aún no se ha usado. - Rechazar las repeticiones recordando los ID de aserción hasta que caduquen.
- Tratar
RelayStatecomo no fiable. Redirige solo a rutas relativas o a una lista de permitidos, o tendrás una redirección abierta.
La lección de una década de vulnerabilidades en SAML: usa una biblioteca mantenida, fija su versión, aplica los parches rápido y nunca escribas tu propia verificación de firmas XML.
SSO iniciado por el IdP y por qué es más arriesgado
En el flujo iniciado por el IdP, el usuario empieza en el IdP, normalmente haciendo clic en el mosaico de tu app en un panel como el portal de Okta o Microsoft My Apps. El IdP envía por POST una respuesta no solicitada a tu URL ACS. No hubo AuthnRequest, así que no hay InResponseTo con el que hacer coincidir.
| Iniciado por el SP | Iniciado por el IdP | |
|---|---|---|
| Empieza en | Tu app | El portal del IdP |
AuthnRequest |
Sí | No |
Comprobación de InResponseTo |
Sí | No es posible |
| Protección frente a CSRF de inicio de sesión | Integrada | Requiere cuidado adicional |
| Enlaces profundos | Mediante RelayState |
Suele aterrizar en una página por defecto |
Sin InResponseTo, un atacante que obtenga una respuesta válida, o que consiga que el navegador de una víctima envíe una, puede iniciar sesión a la víctima en la cuenta del atacante o repetir una respuesta robada dentro de su ventana de validez. Si los clientes necesitan mosaicos en el panel, el patrón más seguro es un mosaico que abra la URL de inicio de sesión de tu app, que a su vez inicia un flujo normal iniciado por el SP. Si tienes que aceptar respuestas no solicitadas, mantén una validez corta, exige ID de aserción de un solo uso y permítelo por cliente, no de forma global.
Metadatos, certificados y configuraciones multi-tenant
El IdP de cada cliente es una relación de confianza aparte. Guarda por tenant: el entity ID del IdP, la URL de inicio de sesión, los certificados de firma y el mapeo de atributos. El diagrama de arquitectura SaaS multi-tenant muestra dónde suele vivir esta configuración por tenant.
Los certificados de firma caducan, y un certificado caducado o rotado es la causa más común de que todo un cliente deje de poder iniciar sesión de repente. Acepta dos certificados por IdP durante un cambio, lee periódicamente la URL de metadatos del IdP si publica una y alerta semanas antes de la caducidad.
SAML autentica; no mantiene sincronizada tu lista de usuarios. Crear cuentas en el primer inicio de sesión es fácil, pero retirar el acceso cuando alguien se va requiere aprovisionamiento, normalmente SCIM 2.0 (RFC 7644). Sin él, un empleado desactivado conserva cualquier sesión que tu app ya haya emitido.
¿SAML u OpenID Connect?
| SAML 2.0 | OpenID Connect | |
|---|---|---|
| Formato | Aserciones XML con firmas XML | JSON, con ID tokens JWT |
| Transporte | Redirecciones del navegador y envíos de formularios | Redirecciones más una solicitud de token por back-channel |
| Apps móviles y SPA | Incómodo | Diseñado para ellas |
| Uso típico | SSO empresarial hacia apps SaaS | Inicio de sesión de consumidores, APIs, SSO empresarial moderno |
Las apps nuevas pueden empezar con OpenID Connect, pero los compradores empresariales seguirán pidiendo SAML, así que la mayoría de los productos B2B admiten ambos. El diagrama de flujo de OAuth 2.0 cubre el protocolo sobre el que se construye OpenID Connect.
El diagrama de SAML SSO
Contenido del diagrama
- User
- Browser
- Service provider (your app)
- Identity provider
- no IdP session yet
- 1. Open the app
- 2. GET /dashboard (no session)
- 3. 302 to IdP: SAMLRequest + RelayState
- 4. GET /sso?SAMLRequest=…&RelayState=… (HTTP-Redirect)
- 5. Login page
- 6. Password + MFA
- 7. HTML form: signed SAMLResponse + RelayState
- 8. POST /saml/acs (HTTP-POST, auto-submit)
- 9. 302 /dashboard + session cookie
- 10. GET /dashboard (cookie)
- 11. 200 OK
Cómo leerlo:
- Cuatro participantes. El usuario, el navegador, tu app como proveedor de servicios y el proveedor de identidad. El tiempo avanza hacia abajo.
- La redirección lleva el
AuthnRequesty elRelayStatede tu app al IdP, a través del navegador. - El fragmento opcional es el paso de inicio de sesión. Con una sesión del IdP ya existente, se omite y el usuario solo ve una redirección rápida.
- El envío del formulario entrega la
SAMLResponsefirmada a la URL ACS. Tu app la valida y fija su propia cookie de sesión. - El bloque Markdown enumera las comprobaciones que debe hacer el endpoint ACS.
Dibujar flujos de SSO que ayuden a depurar
- Dibuja el navegador como un participante propio. SAML es un protocolo de front-channel; ocultar el navegador oculta dónde se pueden interceptar o repetir los mensajes.
- Indica el binding en cada flecha: redirección o POST, y el nombre del parámetro. La mayoría de los errores de configuración son una URL o un binding equivocados.
- Pon los ID en los mensajes. Mostrar que
InResponseTorepite el ID de la solicitud explica por qué se comprueba. - Dibuja el flujo iniciado por el IdP por separado, con su riesgo anotado, en lugar de como una rama del diagrama principal.
El ejemplo de diagrama de secuencia trata con más detalle los participantes, los fragmentos y las etiquetas, y el diagrama de arquitectura zero trust muestra dónde encaja el proveedor de identidad en un modelo de acceso más amplio.
Constrúyelo en Archboard
- Inicia sesión con Google o GitHub y crea un diagrama.
- Añade un Actor del pack Secuencia para el usuario y después tres nodos Línea de vida: Browser, Service provider e Identity provider.
- Dibuja los mensajes de arriba abajo como aristas entre las líneas de vida: la primera solicitud, la redirección con
SAMLRequestyRelayState, la solicitud al IdP, el formulario conSAMLResponse, el POST a la URL ACS y la redirección final con la cookie de sesión. - Añade un Fragmento combinado del pack Secuencia con el operador
optalrededor del paso de inicio de sesión y MFA. - Añade un bloque Markdown del pack Básico con la lista de comprobaciones.
El ejemplo usa 6 nodos de diagrama, dentro del plan Free.
Preguntas frecuentes
¿Qué diferencia hay entre el SSO iniciado por el SP y el iniciado por el IdP? El inicio de sesión iniciado por el SP empieza en tu app, que envía un AuthnRequest al IdP. El iniciado por el IdP empieza en el portal del IdP, que envía por POST una respuesta no solicitada a tu app. El segundo no tiene ninguna solicitud con la que hacer coincidir la respuesta, lo que debilita la protección frente a repeticiones y CSRF de inicio de sesión.
¿Qué es una URL ACS? La URL del Assertion Consumer Service es el endpoint de tu app que recibe la respuesta SAML por HTTP POST. Se configura en el IdP y debe coincidir con los valores Destination y Recipient de cada respuesta.
¿Para qué se usa RelayState? Lleva el lugar al que el usuario quería llegar, para que tu app lo envíe allí después del inicio de sesión. Está limitado a 80 bytes y es una entrada no fiable, así que úsalo como clave de un estado guardado en el servidor o valídalo contra una lista de permitidos.
¿SAML está obsoleto? Es más antiguo que OpenID Connect y más difícil de implementar de forma segura, pero sigue siendo el estándar que esperan muchos proveedores de identidad y compradores empresariales. La mayoría de los productos B2B admiten ambos.
¿SAML elimina a los usuarios cuando dejan la empresa? No. SAML solo autentica al iniciar sesión. El desaprovisionamiento necesita SCIM u otra integración de aprovisionamiento, además de duraciones de sesión razonables en tu app.