privileged.id

SAML

SAML 2.0

How SAML single sign-on works, and the validation rules a service provider must get right, including the errata that changed them.

Reviewed 8 min read 6 primary sources

SAML 2.0 (Security Assertion Markup Language) is the OASIS standard, approved in 2005, that most enterprise single sign-on still runs on. An identity provider (IdP) authenticates the user and sends a signed XML assertion through the user's browser to a service provider (SP), which validates it and creates a session. The standard is split across several documents: Core (assertions and protocols), Bindings (how messages travel), Profiles (how they combine for a use case), and the approved errata, which changed some of the rules quoted below.

SAML's flexibility and its reliance on XML Signature make it easy to validate incorrectly. This page concentrates on what an SP must check.

Assertions, protocols, bindings, profiles

Assertion
An XML statement from an issuer about a subject. It contains an <Issuer>, usually a <ds:Signature>, a <Subject> (the user's <NameID> and how the SP should confirm that the presenter is that subject), <Conditions> (validity window, audience), and statements: an <AuthnStatement> (when and how the user authenticated) and often an <AttributeStatement>.
Protocol
Request and response messages. For SSO: the SP's <samlp:AuthnRequest> and the IdP's <samlp:Response>, which carries one or more assertions.
Binding
How a message is carried over HTTP. The three browser bindings are described below.
Profile
A combination of the above for one use case. The one that matters most is the Web Browser SSO Profile (Profiles §4.1).

The browser bindings

BindingHow the message travelsTypical useSigning
HTTP-RedirectXML is DEFLATE-compressed, base64-encoded, URL-encoded and put in the SAMLRequest or SAMLResponse query parameter of a 302 redirect (Bindings §3.4.4.1).Sending the AuthnRequest to the IdP; logout messages.The XML signature is removed and the query string is signed instead, with SigAlg and Signature parameters over SAMLRequest=…&RelayState=…&SigAlg=….
HTTP-POSTXML is base64-encoded into a hidden form field, SAMLRequest or SAMLResponse, which the browser auto-submits (Bindings §3.5.4).Delivering the Response to the SP's Assertion Consumer Service.XML Signature inside the message.
HTTP-ArtifactThe browser carries only a small reference (SAMLart); the receiver fetches the real message over a direct back-channel call (Bindings §3.6).Keeping assertions out of the browser entirely.The artifact resolution exchange MUST be mutually authenticated, integrity protected and confidential (Profiles §4.1.4.4).

The Web Browser SSO profile forbids the Redirect binding for the IdP's response, because responses typically exceed URL length limits (Profiles §4.1.2, MUST NOT). When a Redirect or POST message is signed, its Destination attribute MUST contain the URL it was sent to, and the recipient MUST verify it matches where the message arrived (Bindings §3.4.5.2, §3.5.5.2).

Web Browser SSO: SP-initiated and IdP-initiated

SAML 2.0 Web Browser SSO, SP-initiated, Redirect then POST bindings Browser Service provider SP Identity provider IdP 1. GET /app (no session) 2. 302 with AuthnRequest 3. GET /sso?SAMLRequest=…&RelayState=… 4. Authenticate the user 5. HTML form with SAMLResponse (auto-POST) 6. POST to ACS URL 7. Validate, then create session
SP-initiated SSO: the SP creates an AuthnRequest with an ID it remembers, and later requires the response's InResponseTo to match that ID.

In SP-initiated SSO the user starts at the application. The SP sends an AuthnRequest (usually via Redirect) and records its ID; the IdP authenticates the user and POSTs a Response to the SP's Assertion Consumer Service (ACS) URL, with InResponseTo set to that ID. The IdP MUST make sure any ACS URL named in a request belongs to the SP it is responding to, or the response can be sent to an attacker (Profiles §4.1.4.1).

In IdP-initiated SSO, the user starts at an IdP portal and clicks an app tile; the IdP sends an unsolicited response with no InResponseTo (Profiles §4.1.5, MUST NOT contain one). The SP has no request to tie the response to, which removes a key protection:

  • An attacker who obtains a valid assertion for themselves can submit it to a victim's browser (login CSRF), signing the victim into the attacker's account.
  • A stolen or intercepted assertion can be replayed until it expires, unless the SP strictly enforces one-time use.
  • Any RelayState sent with it is not tied to anything the SP issued, so an SP that redirects to it blindly becomes an open redirector.

NIST SP 800-63C-4 reflects this: at FAL2 and FAL3 "the federation transaction SHALL be initiated by the RP", and at FAL1 it SHOULD be (SP 800-63C-4 §2.2–2.3). Where you need portal-style launch, a common pattern is for the IdP tile to point at an SP URL that then starts an ordinary SP-initiated flow.

An annotated Response

A successful response as it arrives at the ACS after base64-decoding the SAMLResponse form field. Hosts are example domains; digest and signature values are elided.

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="_r8f3c1a29e04b7d6"
    Version="2.0"
    IssueInstant="2026-10-26T14:02:11Z"
    Destination="https://sp.example.com/saml/acs"
    InResponseTo="_a71e5c0d9b3f4e28">
  <saml:Issuer>https://idp.example.org/saml</saml:Issuer>
  <samlp:Status>
    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </samlp:Status>
  <saml:Assertion ID="_a5b9e2c47d10f3e8" Version="2.0" IssueInstant="2026-10-26T14:02:11Z">
    <saml:Issuer>https://idp.example.org/saml</saml:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
      <ds:SignedInfo>
        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
        <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
        <ds:Reference URI="#_a5b9e2c47d10f3e8">
          <ds:Transforms>
            <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
            <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          </ds:Transforms>
          <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
          <ds:DigestValue>(elided)</ds:DigestValue>
        </ds:Reference>
      </ds:SignedInfo>
      <ds:SignatureValue>(elided)</ds:SignatureValue>
    </ds:Signature>
    <saml:Subject>
      <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">8c1f5e0a-3b7d-4c62-9e4a-2d9b6f1a7c30</saml:NameID>
      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData
            Recipient="https://sp.example.com/saml/acs"
            NotOnOrAfter="2026-10-26T14:07:11Z"
            InResponseTo="_a71e5c0d9b3f4e28"/>
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions NotBefore="2026-10-26T14:01:41Z" NotOnOrAfter="2026-10-26T14:07:11Z">
      <saml:AudienceRestriction>
        <saml:Audience>https://sp.example.com/saml</saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AuthnStatement AuthnInstant="2026-10-26T14:01:58Z" SessionIndex="_s4d2e9f61">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>
    <saml:AttributeStatement>
      <saml:Attribute Name="email">
        <saml:AttributeValue>jane@example.com</saml:AttributeValue>
      </saml:Attribute>
    </saml:AttributeStatement>
  </saml:Assertion>
</samlp:Response>
  1. Destination on the Response: the ACS URL. If present, the SP MUST check it matches where the message was received, or discard it (Core §3.2.2).
  2. InResponseTo: the ID of the SP's AuthnRequest. It appears on the Response and on the bearer SubjectConfirmationData.
  3. Status: anything other than Success means no assertion should be trusted; error responses MUST NOT include assertions (Profiles §4.1.4.2).
  4. ds:Signature inside the Assertion: an enveloped signature whose single Reference points at the assertion's own ID (#_a5b9e2c47d10f3e8), as Core §5.4.2 requires.
  5. NameID: the subject. A persistent identifier is an opaque, stable per-SP value; prefer it over email as the account key.
  6. SubjectConfirmation Method="…:cm:bearer" with Recipient (the ACS URL), NotOnOrAfter (delivery deadline) and InResponseTo. It MUST NOT contain NotBefore.
  7. Conditions: the validity window, and an AudienceRestriction naming the SP's entity ID. Each bearer assertion MUST contain one (Profiles §4.1.4.2).
  8. AuthnStatement: when and how the user authenticated; SessionIndex is used for single logout.

The SAML decoder base64-decodes and inflates SAMLRequest and SAMLResponse values so you can read real messages.

What the SP must validate

The Web Browser SSO profile (§4.1.4.3, as amended by Errata 05) and Core give the checks. All of them, every time:

  • Signature. Verify the signatures present on the assertion(s) or the response, against the IdP's key from metadata (not from KeyInfo in the message). With the HTTP-POST binding, every assertion must be covered by a signature: the original 2005 text required each assertion to be signed; SAML V2.0 Errata 05 (E26) replaced that with "each assertion MUST be protected by a digital signature", achieved "by signing each individual <Assertion> element or by signing the <Response> element".
  • The signed element is the one you use. The signature's Reference must point at the exact Assertion (or Response) you read the subject and attributes from. See XML Signature Wrapping.
  • Encrypted assertions. If an EncryptedAssertion uses a CBC-mode algorithm, the Response SHOULD be signed so the ciphertext is integrity-protected before decryption (Errata 05, E93), and relying parties SHOULD require that protection.
  • Issuer. Each assertion's Issuer is the IdP's entity ID, and all assertions in a response come from the same IdP (Errata 05, E26).
  • Audience. The AudienceRestriction includes your SP entity ID. The condition is valid only if you are a member of the audience (Core §2.5.1.4).
  • Recipient. The bearer SubjectConfirmationData/@Recipient equals the ACS URL the response was delivered to (MUST).
  • Destination. If present (and it must be, if the message is signed), it equals the ACS URL.
  • InResponseTo. Equals the ID of an AuthnRequest you sent from this browser session, and has not been used before. For unsolicited responses it must be absent.
  • Time. SubjectConfirmationData/@NotOnOrAfter has not passed, and Conditions NotBefore/NotOnOrAfter hold, with small allowed clock skew.
  • One-time use. For POST, the SP MUST prevent replay by keeping the IDs of used assertions until their NotOnOrAfter passes (Profiles §4.1.4.5). <OneTimeUse> in Conditions additionally says the assertion MUST NOT be retained for future use (Core §2.5.1.5).
  • Status is Success, and all conditions present are understood; a condition that is not understood makes the assertion Indeterminate, and an Invalid or Indeterminate assertion MUST be rejected (Core §2.5.1.1).
  • Session lifetime. If the AuthnStatement has SessionNotOnOrAfter, the SP SHOULD end its session by then.

An assertion that fails any check, or whose subject confirmation cannot be met, SHOULD be discarded and not used to establish a session (Profiles §4.1.4.3). Treat that SHOULD as a must in practice.

XML Signature Wrapping

XML Signature does not sign "the document". It signs whatever element its Reference URI points to, located by ID. A SAML library therefore does two separate things: the signature module finds the referenced element and verifies it, and the business logic finds an assertion and reads the user from it. If those two lookups can return different elements, an attacker can move the genuinely signed assertion somewhere the business logic does not look (inside an extension or wrapper element) and insert their own unsigned assertion where it does.

XML Signature Wrapping: the verified element is not the element the application uses What the IdP signed <Response> <Assertion ID="a1"> Subject: alice Signature → #a1 verifies What the attacker sends <Response> <Assertion ID="evil"> Subject: admin (unsigned) wrapper element original a1 + Signature still verifies Signature check finds #a1 and passes; the app reads the first assertion.
The signature over #a1 still verifies after the attacker relocates it. An SP that then reads "the first assertion" uses the forged one.

The attack class was analysed in depth by Somorovsky and colleagues in "On Breaking SAML: Be Whoever You Want to Be" (USENIX Security 2012), who found critical wrapping vulnerabilities in 11 of the 14 SAML frameworks they examined. Variants include duplicate IDs, assertions nested inside other assertions, and comments or namespace tricks that make two parsers disagree.

Defenses:

  • Use a maintained SAML library; do not hand-roll XML signature handling.
  • Have the signature step return the element it verified, and read the subject and attributes only from that element, never by a separate search of the document.
  • Validate the message against the SAML schema and reject documents with duplicate ID values or more assertions than you expect.
  • Follow Core §5.4: exactly one Reference, pointing at the root of the signed assertion or message; reject transforms other than enveloped-signature and exclusive canonicalization (verifiers MAY reject others, and if they do not, MUST ensure no content is excluded from the signature, §5.4.4).
  • Take the IdP's signing key from configured metadata, never from the certificate embedded in the message.
  • Prefer SHA-256-based signature algorithms. SAML Core (2005) names rsa-sha1 as the algorithm processors should support, which reflects its age; it is not a reason to accept SHA-1 today.

RelayState

RelayState is an opaque value the SP can send with its request and get back unchanged with the response, typically to remember which page the user wanted. The bindings say it MUST NOT exceed 80 bytes and SHOULD be integrity-protected by its creator, for example with a checksum or by using a random value as a key to server-side state (Bindings §3.4.3, §3.5.3). The profile adds that the SP SHOULD reveal as little of the original request in it as possible (Profiles §4.1.3.1).

The practical rule: never put a raw URL in RelayState and redirect to whatever comes back. Store the target server-side under a random key, or validate the returned value against an allow-list of your own paths.

Checklist

  • SP-initiated flows only where possible; if IdP-initiated is enabled, strict replay protection and safe RelayState handling.
  • IdP metadata (entity ID, signing certificates, endpoints) configured from a trusted source and refreshed on rotation.
  • Signatures required on every assertion or on the response; the verified element is the one consumed.
  • Audience, Recipient, Destination, InResponseTo, time window and one-time use all checked.
  • Accounts keyed on a persistent NameID from a specific IdP, not on an email attribute.
  • Encrypted assertions accompanied by a signed response when CBC algorithms are in use.
  • For new applications, consider OpenID Connect; see the comparison.

Sources

  1. Assertions and Protocols for the OASIS Security Assertion Markup Language (SAML) V2.0, OASIS. OASIS specification (cited, brief quotations).
  2. Bindings for the OASIS Security Assertion Markup Language (SAML) V2.0, OASIS. OASIS specification (cited, brief quotations).
  3. Profiles for the OASIS Security Assertion Markup Language (SAML) V2.0, OASIS. OASIS specification (cited, brief quotations).
  4. SAML V2.0 Errata 05, OASIS. OASIS specification (cited, brief quotations).
  5. NIST SP 800-63C-4: Digital Identity Guidelines, Federation and Assertions, NIST. Public domain (U.S. government work).
  6. On Breaking SAML: Be Whoever You Want to Be (Somorovsky et al., USENIX Security 2012), USENIX. Cited for facts; not reproduced.