Understand SAML

SAML in detail

The messages, signatures and checks an application must perform, with what ID-Kit does at each step.

1. Roles and vocabulary

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.

2. Metadata

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>
  • KeyDescriptor use="signing": the certificate whose public key is used to verify signatures. use="encryption" is for encryption.
  • SingleSignOnService (IdP): where to send a request, with the accepted binding.
  • AssertionConsumerService (SP): where to receive the response. WantAssertionsSigned indicates that the SP requires a signed assertion.
  • Certificate renewal: publish both certificates (old and new) during the transition. An expired certificate with no published replacement blocks all logins at once.

3. The two login scenarios

Started by the application (SP-initiated), the most common

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.

Started by the identity provider (IdP-initiated)

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.

4. Bindings: how messages travel

The browser carries the messages. A binding describes how:

BindingPrincipeUsage
HTTP-RedirectThe 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-POSTThe 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-ArtifactOnly a reference is sent; the content is fetched directly server to server.Rare

5. The request (AuthnRequest)

<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).

6. The response and the assertion

<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.

7. The XML signature

  • What to sign: the Response, the Assertion, or both. Signing the assertion is the reasonable minimum: it is what carries the identity.
  • "Enveloped": the signature is placed inside the element it signs (right after the Issuer), and excluded from the computation.
  • Canonicalisation (exc-c14n): before computing the digest, the XML is normalised (attribute order, namespaces, escaping) so that harmless differences, such as a line break or a different prefix, do not invalidate the signature.
  • Two computations: a digest of the element, then the RSA signature of the SignedInfo block that contains that digest.
  • Algorithms: RSA-SHA256 is the current standard. SHA-1 is obsolete: keep it only for legacy systems.
  • The certificate inside the signature proves nothing: an attacker can put their own there. The application must compare it with the trusted certificate obtained from the IdP's metadata.

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.

8. Checks on the application side

In order, a serious application checks:

  1. The status is Success.
  2. The signature is valid, made with a trusted certificate, and does cover the element that will be read (protection against "signature wrapping").
  3. Destination : the application's ACS URL.
  4. InResponseTo: matches a request that was sent and not yet consumed (unless IdP-initiated is allowed).
  5. Issuer : the expected IdP.
  6. Audience : the application's Entity ID.
  7. NotBefore / NotOnOrAfter: valid now, with a small clock tolerance (a few minutes).
  8. SubjectConfirmation: bearer method, Recipient = ACS URL, not expired.
  9. Anti-replay: the assertion ID has not already been used.
  10. Only then: read the NameID and the attributes.

ID-Kit's test SP performs all these checks and shows them one by one, with the fix to apply.

9. NameID and attributes

  • NameID formats: emailAddress (readable, but the address can change), persistent (stable identifier specific to the IdP/SP pair, preferred as an account key), transient (changes on every login), unspecified.
  • Attributes: name/value pairs, sometimes multi-valued (groups). The exact name matters (email, groups, or a long URI-style name at Microsoft). The IdP decides which attributes are sent, and the application must map them.

10. Encryption and logout

  • EncryptedAssertion: the IdP can encrypt the assertion with the SP's encryption certificate. Useful when the response passes through an intermediary you do not control. Within an end-to-end HTTPS connection, the signature is generally enough.
  • Single Logout (SLO): LogoutRequest/LogoutResponse between SP and IdP, matched by the SessionIndex. In practice, it is the least well and least uniformly implemented part of the standard.

11. Security: the classic pitfalls

  • XML Signature Wrapping: the attacker slips a fake assertion next to the genuine signed one. Defence: check that the signature references exactly the element being read, and that IDs are unique.
  • XXE: an XML parser that follows external entities can read files. Defence: forbid DOCTYPE and ENTITY (ID-Kit does).
  • Replay: a stolen assertion is reused. Defence: short lifetime, InResponseTo, memory of consumed IDs.
  • Missing signature accepted: an application that "verifies a signature if one exists" but does not require it to be present can be bypassed.
  • Audience and Recipient ignored: an assertion issued for another application would be accepted.
  • Unchecked RelayState: open redirect. Defence: only accept internal addresses.

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.