Entender SAML
Los mensajes, firmas y comprobaciones que debe realizar una aplicación, con lo que hace ID-Kit en cada paso.
SAML 2.0 (2005) es un estándar de OASIS para la federación de identidades. El proveedor de identidad (IdP) autentica al usuario y emite aserciones; el proveedor de servicio (SP) las consume en la dirección llamada ACS (Assertion Consumer Service). Cada parte tiene un identificador único, el Entity ID, a menudo una URL que no necesita ser accesible.
Un documento XML describe a cada parte: su Entity ID, sus endpoints, sus certificados. Importarlos evita la mayoría de los errores de copia.
<md:EntityDescriptor entityID="https://idp.example/idp">
<md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">...<ds:X509Certificate>MIIC...</ds:X509Certificate>...</md:KeyDescriptor>
<md:SingleSignOnService Binding="...HTTP-Redirect" Location="https://idp.example/sso"/>
<md:SingleSignOnService Binding="...HTTP-POST" Location="https://idp.example/sso"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>
El usuario abre la aplicación, que envía un AuthnRequest. La respuesta cita el ID de esa solicitud en InResponseTo: la aplicación puede comprobar que realmente pidió esta respuesta. El parámetro RelayState permite al usuario volver a la página que pidió originalmente.
El usuario hace clic en un icono del portal de su organización. No hubo solicitud previa: la respuesta llega «no solicitada» y no tiene InResponseTo. Es cómodo pero menos seguro (la aplicación no puede vincular la respuesta con una solicitud) y algunas aplicaciones lo rechazan.
El navegador lleva los mensajes. Un binding describe cómo:
| Binding | Principe | Usage |
|---|---|---|
| HTTP-Redirect | El XML se comprime (deflate), se codifica en base64 y se coloca en la URL (?SAMLRequest=…). Si la solicitud está firmada, la firma queda fuera del XML, en los parámetros SigAlg y Signature, y cubre la cadena SAMLRequest=…&RelayState=…&SigAlg=… tal como se envió. | Solicitudes (tamaño limitado por la URL) |
| HTTP-POST | El XML se codifica en base64 en un formulario HTML que el navegador envía automáticamente. La firma está en el XML. | Respuestas (casi siempre) y solicitudes |
| HTTP-Artifact | Solo se envía una referencia; el contenido se obtiene directamente de servidor a servidor. | Rare |
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_a1b2c3" <!-- identifiant unique, repris dans la réponse -->
IssueInstant="2026-11-01T10:00:00Z"
Destination="https://idp.example/sso" <!-- à qui la demande est adressée -->
AssertionConsumerServiceURL="https://app.example/saml/acs" <!-- où renvoyer la réponse -->
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://app.example/metadata</saml:Issuer> <!-- l'Entity ID de l'application -->
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
</samlp:AuthnRequest>
Opciones útiles: ForceAuthn (reautenticar aunque exista sesión), IsPassive (no mostrar nada, fallar si el usuario no ha iniciado sesión ya), NameIDPolicy (formato de identificador deseado), RequestedAuthnContext (nivel de autenticación exigido, por ejemplo dos factores).
<samlp:Response Destination="https://app.example/saml/acs" InResponseTo="_a1b2c3" ...>
<saml:Issuer>https://idp.example/idp</saml:Issuer>
<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
<saml:Assertion ID="_z9y8" IssueInstant="..." Version="2.0">
<saml:Issuer>https://idp.example/idp</saml:Issuer>
<ds:Signature>...</ds:Signature> <!-- le sceau -->
<saml:Subject>
<saml:NameID Format="...emailAddress">jane@example.com</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData InResponseTo="_a1b2c3" Recipient="https://app.example/saml/acs" NotOnOrAfter="..."/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="..." NotOnOrAfter="...">
<saml:AudienceRestriction><saml:Audience>https://app.example/metadata</saml:Audience></saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="..." SessionIndex="_z9y8">...</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="groups"><saml:AttributeValue>admins</saml:AttributeValue></saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
Dos niveles: la Response (el sobre: estado, destino) y la Assertion (el contenido que importa). El Status dice si la identificación ha tenido éxito; si falla, no hay aserción.
Issuer) y se excluye del cálculo.SignedInfo que contiene ese resumen.ID-Kit puede firmar la aserción, la respuesta o ambas, con SHA-1/256/384/512, y simular casos defectuosos para comprobar que su aplicación los rechaza.
Por orden, una aplicación seria comprueba:
Recipient = URL del ACS, no caducado.El SP de pruebas de ID-Kit realiza todas estas comprobaciones y las muestra una a una, con la corrección que hay que aplicar.
email, groups o un nombre largo estilo URI en Microsoft). El IdP decide qué atributos se envían y la aplicación debe mapearlos.SessionIndex. En la práctica, es la parte menos y peor implementada del estándar.InResponseTo, memoria de los ID consumidos.Para probar estos comportamientos en la práctica, use las simulaciones de error del IdP de ID-Kit: su aplicación debe rechazar cada una.
¿Quiere verlo en directo? ID-Kit hace el papel que le falta y le muestra cada paso.