Lakshya

Identity & Access · Chapter 10 of 16

SAML and enterprise federation

XML, assertions, and three signature attacks that broke real products. Still the backbone of enterprise SSO, and worth understanding precisely rather than avoiding.

7 min read1 diagramAll 16 chapters

SAML is older than OAuth, is built on XML, and is not going away — because it is embedded in every enterprise application procured in the last twenty years. If you work in identity you will meet it, and the parts worth knowing are the assertion structure, the trust model, and why its security failures were structural rather than incidental.

SP-INITIATED SAML — THE ENTERPRISE DEFAULT 1 hit the appno session2 SP builds AuthnRequestredirect or POST binding3 IdP authenticatesMFA lives here4 signed Assertion POSTed backto the ACS URL WHAT IS INSIDE THE ASSERTION <Issuer> who made this claim<Subject> + <NameID> who the user is<SubjectConfirmationData> Recipient, NotOnOrAfter<Conditions> NotBefore, NotOnOrAfter, <AudienceRestriction><AuthnStatement> how they authenticated, and when<AttributeStatement> groups, email, whatever you mapped<Signature> over the assertion, the response, or both THREE ATTACK CLASSES THAT HAVE BROKEN REAL PRODUCTS XML signature wrapping — a second, unsigned assertion is smuggled in; the library verifies one element and reads another. Comment truncation — admin<!--x-->@corp.com. The parser returns only the first text node, so it reads as a different user. Unverified signer — signature valid, but against a certificate the attacker supplied. Valid ≠ from the IdP you trust. The lesson is not “SAML is insecure”. It is that signing a subset of a mutable XML document is a fragile idea, and every one of thesebugs came from parse-then-verify rather than verify-then-parse. Never write your own SAML. Use a maintained library, pin the IdP certificate, require the assertion itself to be signed, and checkAudience, Recipient and NotOnOrAfter. And know why you would choose OIDC instead: JSON and JWT rather than XML and XML-DSig, a smaller attack surface, and no canonicalisationproblem at all.
The SP-initiated flow, the assertion's contents, and the three attack classes. Every one of those attacks came from the same root cause: parsing a document before verifying it.

The two parties and the trust model

  • Identity Provider (IdP) — authenticates the user and issues a signed assertion. Entra, Okta, Ping, ADFS, Keycloak.
  • Service Provider (SP) — the application. It trusts assertions signed by a specific certificate belonging to a specific IdP.
  • Trust is established out of band, by exchanging metadata: entity IDs, endpoints and the signing certificate. There is no discovery and no dynamic registration. This is why SAML integrations involve a spreadsheet and a support ticket, and why certificate rotation is an event rather than a routine.

SP-initiated versus IdP-initiated, and why one is worse

SP-initiated: the user hits the application, which redirects to the IdP with an AuthnRequest, and the IdP posts an assertion back to the application's Assertion Consumer Service URL. This is the default and the safer one, because the SP started the flow and can correlate the response to its own request via the InResponseTo field.

IdP-initiated: the user clicks a tile in a portal and the IdP posts an unsolicited assertion. Convenient, widely deployed, and structurally weaker — there is no request to correlate against, so the SP cannot distinguish an assertion it expected from one an attacker replayed. If you can avoid IdP-initiated, do. If you cannot, the compensating controls are strict single-use enforcement on assertion IDs, a very short NotOnOrAfter, and rejecting any assertion whose Recipient is not your exact ACS URL.

What to validate on every assertion

  • The signature, against the pinned IdP certificate — not against whatever certificate the document happens to embed. ‘The signature is valid’ and ‘the signature is from the party I trust’ are different statements, and conflating them has been a real vulnerability in multiple products.
  • That the assertion itself is signed, not merely the response wrapping it. Signing the response alone leaves the assertion swappable.
  • AudienceRestriction names your entity ID. Without this, an assertion issued for a different application is accepted by yours.
  • Recipient matches your ACS URL exactly.
  • NotBefore and NotOnOrAfter, with tight skew.
  • The assertion ID has not been seen before — replay protection requires state, and this is the check implementations most often skip because it is the only one that requires a store.
  • InResponseTo matches a request you actually made, for SP-initiated flows.

The three attack classes, and the lesson underneath them

XML Signature Wrapping. XML signatures cover a referenced subtree, not the whole document. An attacker moves the legitimately signed assertion into a location the signature verifier still finds, and inserts an unsigned forged assertion where the application's business logic reads. Signature verification passes on the real element; the application acts on the fake one. This broke multiple major SSO implementations.

Comment truncation (the ‘golden SAML’ family of parser bugs). An attacker with a legitimate account inserts an XML comment into the NameID: admin<!--x-->@corp.com. Canonicalisation for signing sees the full string, but many XML libraries return only the text node before the comment when reading the value — so the signature is valid over one string and the application reads another. A vulnerability in a text-node API became an authentication bypass across many products simultaneously.

Unverified signer. Accepting the certificate embedded in the assertion rather than the one you configured. The signature verifies perfectly, against the attacker's own key.

The lesson. All three come from the same structural problem: cryptographically signing a subset of a mutable, canonicalisable document, then parsing that document with a general-purpose library. Verify first, then extract, and extract from the same element that was verified — never from a re-parse. And do not write your own; use a maintained library that has already been through these.

SAML versus OIDC, answered as a design decision

DimensionSAMLOIDC
EncodingXML with XML-DSigJSON with JWT
Attack surfaceCanonicalisation, wrapping, parser inconsistencyAlgorithm confusion, missing claim validation
Client typesBrowser-based web applicationsWeb, mobile, single-page, native, machine
Trust setupMetadata exchange, out of bandDiscovery document, dynamic registration possible
Session semanticsRich — SLO, session index, authentication contextSimpler; back-channel logout is a later addition and unevenly supported
Where it livesEnterprise SaaS procured over two decadesAnything built recently, and everything mobile
Choose it whenThe application only supports SAML — which is the usual reasonYou have a choice

The mature position for an interview: “New integrations get OIDC. SAML stays where the application requires it, which is a large installed base and will be for years. The migration is driven by the application vendor, not by us, so identity teams run both indefinitely and the operational cost is real — two sets of certificates, two rotation processes, two sets of logout semantics.”

The certificate rotation that takes companies down

The IdP's signing certificate expires, typically after one to three years. When it does, every SP that pinned it rejects every assertion, and everyone loses access to everything at the same moment. It is the identity equivalent of the TLS expiry outage and it is more disruptive, because SSO is the front door to everything.

The controls are simple and frequently absent: an inventory of every SP and its configured certificate; alerting well before expiry; and where the SP supports it, publishing both the old and new certificates in metadata during an overlap window so the rotation is not a flag-day. Being the person who asks “when does our IdP signing certificate expire, and who holds the list of relying parties?” is a very cheap way to be useful.

Check yourself

Why is IdP-initiated SAML weaker than SP-initiated?There is no request to correlate the response against, so an unsolicited assertion cannot be distinguished from a replayed one. Compensate with strict single-use IDs, short lifetimes and exact Recipient matching.
What is XML signature wrapping?Moving the signed element so verification still finds it while inserting an unsigned forgery where the application reads. Verification passes on one element and logic acts on another.
Why is 'the signature is valid' not sufficient?It may be valid against a certificate the attacker supplied. You must verify against the certificate you configured for that IdP.
What breaks when an IdP signing certificate expires?Every relying party at once. Keep an inventory, alert early, and use a metadata overlap window where the SP supports it.
← OAuth 2.0 and OIDC, properlyKerberos, LDAP and Active Directory →