Comprendre SAML
Les messages, les signatures et les vérifications qu'une application doit faire, avec ce que fait ID-Kit à chaque étape.
SAML 2.0 (2005) est un standard OASIS de fédération d'identité. Le fournisseur d'identité (IdP) authentifie l'utilisateur et émet des assertions ; le fournisseur de service (SP) les consomme à l'adresse nommée ACS (Assertion Consumer Service). Chaque partie a un identifiant unique, l'Entity ID, souvent une URL qui n'a pas besoin d'être joignable.
Un document XML décrit chaque partie : son Entity ID, ses points d'accès, ses certificats. Les importer évite la plupart des erreurs de recopie.
<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>
L'utilisateur ouvre l'application, qui envoie une AuthnRequest. La réponse cite l'identifiant de cette demande dans InResponseTo : l'application peut vérifier qu'elle a bien demandé cette réponse. Le paramètre RelayState permet de revenir à la page demandée au départ.
L'utilisateur clique sur une tuile dans le portail de son organisation. Aucune demande n'a précédé : la réponse arrive « sans demande » et n'a pas d'InResponseTo. C'est pratique, mais moins sûr (l'application ne peut pas relier la réponse à une demande), et certaines applications le refusent.
Le navigateur transporte les messages. Un binding décrit comment :
| Binding | Principe | Usage |
|---|---|---|
| HTTP-Redirect | Le XML est compressé (deflate), encodé en base64 puis placé dans l'URL (?SAMLRequest=…). Si la demande est signée, la signature est hors du XML, dans les paramètres SigAlg et Signature, et porte sur la chaîne SAMLRequest=…&RelayState=…&SigAlg=… telle qu'envoyée. | Demandes (taille limitée par l'URL) |
| HTTP-POST | Le XML est encodé en base64 dans un formulaire HTML que le navigateur envoie automatiquement. La signature est dans le XML. | Réponses (presque toujours) et demandes |
| HTTP-Artifact | On n'envoie qu'une référence, le contenu est récupéré directement serveur à serveur. | 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>
Options utiles : ForceAuthn (réauthentifier même si une session existe), IsPassive (ne rien afficher, échouer si l'utilisateur n'est pas déjà connecté), NameIDPolicy (format d'identifiant souhaité), RequestedAuthnContext (niveau d'authentification exigé, par exemple la double authentification).
<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>
Deux niveaux : la Response (enveloppe : statut, destination) et l'Assertion (le contenu qui compte). Le Status dit si l'identification a réussi ; en cas d'échec il n'y a pas d'assertion.
Issuer), et exclue du calcul.SignedInfo qui contient ce condensat.ID-Kit peut émettre des signatures sur l'assertion, la réponse ou les deux, avec SHA-1/256/384/512, et simuler les cas défectueux pour vérifier que votre application les refuse.
Dans l'ordre, une application sérieuse contrôle :
Recipient = URL ACS, pas expirée.Le SP de test d'ID-Kit effectue tous ces contrôles et vous les affiche un par un, avec la correction à apporter.
email, groups, ou un long nom de type URI chez Microsoft). C'est l'IdP qui décide quels attributs partent, et l'application qui doit les mapper.SessionIndex. En pratique, c'est la partie la moins bien et la moins uniformément implémentée du standard.InResponseTo, mémoire des identifiants consommés.Pour tester concrètement ces comportements, utilisez les simulations d'erreur de l'IdP ID-Kit : chacune doit être refusée par votre application.
Envie de voir ça en vrai ? ID-Kit joue le rôle qui vous manque et vous montre chaque étape.