Understand SAML
The messages, signatures and checks an application must perform, with what ID-Kit does at each step.
SAML 2.0 (2005) is an OASIS standard for identity federation. The identity provider (IdP) authenticates the user and issues assertions; the service provider (SP) consumes them at the address called the ACS (Assertion Consumer Service). Each party has a unique identifier, the Entity ID, often a URL that does not need to be reachable.
An XML document describes each party: its Entity ID, its endpoints, its certificates. Importing them avoids most copying mistakes.
<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>
The user opens the application, which sends an AuthnRequest. The response cites that request's ID in InResponseTo: the application can check that it did ask for this response. The RelayState parameter lets the user return to the page originally requested.
The user clicks a tile in their organisation's portal. No request came before: the response arrives "unsolicited" and has no InResponseTo. It is convenient but less safe (the application cannot link the response to a request), and some applications refuse it.
The browser carries the messages. A binding describes how:
| Binding | Principe | Usage |
|---|---|---|
| HTTP-Redirect | The XML is compressed (deflate), base64-encoded, then placed in the URL (?SAMLRequest=…). If the request is signed, the signature is outside the XML, in the SigAlg and Signature parameters, and covers the string SAMLRequest=…&RelayState=…&SigAlg=… as sent. | Requests (size limited by the URL) |
| HTTP-POST | The XML is base64-encoded in an HTML form that the browser submits automatically. The signature is in the XML. | Responses (almost always) and requests |
| HTTP-Artifact | Only a reference is sent; the content is fetched directly server to server. | 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>
Useful options: ForceAuthn (re-authenticate even if a session exists), IsPassive (show nothing, fail if the user is not already logged in), NameIDPolicy (desired identifier format), RequestedAuthnContext (required authentication level, for example two-factor).
<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>
Two levels: the Response (envelope: status, destination) and the Assertion (the content that matters). The Status says whether identification succeeded; on failure there is no assertion.
Issuer), and excluded from the computation.SignedInfo block that contains that digest.ID-Kit can sign the assertion, the response or both, with SHA-1/256/384/512, and simulate faulty cases to check that your application rejects them.
In order, a serious application checks:
Recipient = ACS URL, not expired.ID-Kit's test SP performs all these checks and shows them one by one, with the fix to apply.
email, groups, or a long URI-style name at Microsoft). The IdP decides which attributes are sent, and the application must map them.SessionIndex. In practice, it is the least well and least uniformly implemented part of the standard.InResponseTo, memory of consumed IDs.To test these behaviours in practice, use the ID-Kit IdP's error simulations: each one must be rejected by your application.
Want to see it for real? ID-Kit plays the role you are missing and shows you each step.