Why architecture matters here
The first architectural fact is that SAML moves the authentication decision out of your application permanently, and that is the point rather than a side effect. Your app stops having passwords. It stops having a password reset flow, a credential store, a brute-force lockout policy, and an MFA implementation. Those become someone else's problem — specifically, the identity provider's, which is an organisation that has staffed a team for it. The security surface you delete by adopting federation is usually much larger than the surface you add.
The second is that it moves lifecycle out too, and this is the part that sells it internally. When an employee leaves, the organisation disables one account at the identity provider and every federated application becomes inaccessible at their next login. Without federation, offboarding is a checklist against a list of applications that is always out of date, and the long tail of forgotten accounts in forgotten tools is where real breaches start. Centralised deprovisioning is the single most valuable property of enterprise SSO and the reason procurement asks for it.
The third is that the trust boundary becomes cryptographic rather than topological, which is a genuine inversion of how most engineers think about security. There is no firewall rule here, no VPC peering, no allowlisted IP. The SP and the IdP may be on opposite sides of the internet with no route between them, and it does not matter. What matters is that the SP holds the IdP's public certificate and checks it. Everything that looks like a network security control in a SAML deployment is decoration; the certificate is the control.
The fourth is that SAML's job is narrower than people assume, and the confusion causes real bugs. SAML authenticates — it establishes who the user is, once, at login. It is not a session mechanism and not an authorisation mechanism. The moment the SP validates the assertion, it mints its own local session and SAML is finished; the assertion's five-minute lifetime has no relationship to how long the user stays logged in. And while assertions carry attributes that often drive authorisation decisions, the SP is the one making those decisions. Treating the IdP as the authorisation authority means trusting attribute values you did not compute for access control you cannot audit.
The architecture: every piece explained
The Service Provider is your application. It owns no credentials and performs no authentication. Its entire SAML surface is two things: the ability to construct an AuthnRequest, and an endpoint called the Assertion Consumer Service (ACS) that receives assertions. The ACS is the most security-critical URL in the application by a wide margin — it is the place where an unauthenticated HTTP POST from an untrusted browser becomes an authenticated session, and every check that stands between those two states lives in it.
The Identity Provider owns authentication. It has the password database, the MFA, the risk engine, the session with the user, and the signing key. It mints assertions. Critically, it decides what to put in them — which attributes, under which names, in which formats — and that decision is made in a system your team probably does not administer. A great deal of SAML integration work is not code; it is asking an identity team to release a group attribute.
The assertion is the payload and the only thing that matters. It is XML containing a subject (who the user is, via a NameID), an attribute statement (email, groups, whatever was released), conditions (an audience restriction naming the intended SP, and a validity window), an authn statement (when and how they authenticated), and a signature. The signature covers the assertion, the response, or both — and which one it covers is not cosmetic, because validating a signature on the wrong element is the root of a whole vulnerability family.
Metadata is how the two parties learn about each other, and it is underrated. It is an XML document that carries the entityID, the endpoints, the NameID formats supported, and — crucially — the signing certificate. Exchanging metadata is establishing trust; consuming it from a URL rather than pasting a certificate into a config field is what makes certificate rotation a non-event rather than an outage. The single most common SAML incident in production is an expired IdP certificate at a site that pasted it in by hand two years ago.
Bindings are how messages travel, and the choice has security content. HTTP-Redirect puts a deflated, base64-encoded message in a query parameter — fine for the request, which carries nothing sensitive, and it lands in every access log along the way. HTTP-POST puts a base64 blob in a form field that auto-submits via JavaScript, which is how assertions travel, because they are too big for a URL and should not be in one. The relay state rides alongside as the SP's own opaque token — nominally 'where the user was going', and an open redirect if you put a raw URL in it and honour it on return.
End-to-end flow
The user hits a protected page on the SP with no local session. The SP does not challenge them. It constructs an AuthnRequest — an XML document naming itself as the issuer and stating where it wants the assertion delivered — deflates and encodes it, stashes a relay state describing where the user was headed, and sends a redirect to the IdP's SSO URL with the request as a query parameter. The SP now has no further involvement until something comes back. It holds no state except what it can recognise later.
At the IdP the user authenticates, or more often does not need to: the IdP likely has an existing session from another application, which is the entire user-visible value of SSO. Whatever the IdP does here — password, MFA, device trust, risk scoring — is opaque to the SP. The IdP looks up the SP by its entityID, checks the request's ACS URL against the one it has on file (this check is why a compromised SP cannot redirect assertions elsewhere), decides which attributes to release under the configured policy, and builds an assertion.
The IdP signs it. It sets the audience restriction to the SP's entityID, sets a validity window that is short by design, sets the recipient to the ACS URL, and signs the XML with its private key. Then it returns an HTML page to the browser containing a form whose action is the SP's ACS URL, whose hidden field holds the base64 assertion, and whose body has a script that submits it immediately. The user sees a flicker. The browser POSTs the assertion to the SP.
Now the ACS does the only work that matters, and the order is not arbitrary. It parses the XML. It validates the signature against the trusted certificate from metadata — not against a certificate embedded in the document, which would be circular, and this is a real implementation bug. It confirms the signature covers the element it is about to read. It checks the audience restriction names this SP, so an assertion minted for a different application cannot be replayed here. It checks the time window against its own clock. It checks the recipient matches this ACS. It checks the assertion ID against a replay cache. Only if every one of these passes does it extract the subject and attributes.
Then SAML is over. The SP maps the NameID to a local user — creating one on the fly if it does JIT provisioning, which most modern SPs do — applies whatever attribute-to-role mapping it owns, and mints its own session cookie. From this moment the user's access is governed entirely by that cookie and the SP's session policy. The assertion is discarded. Its five-minute lifetime constrained nothing but the window in which it could be presented. The SP redirects to the relay state's destination and the user lands where they were going.
Logout is where the elegance runs out, and it is worth knowing before you promise it. Single Logout requires the IdP to notify every SP the user has a session with, which means it must track them all, and each SP must implement a logout endpoint and honour it. In practice front-channel logout depends on chained browser redirects that fail silently when any SP is slow or unreachable, and back-channel logout requires direct connectivity the federation model explicitly does not assume. The honest position: authentication federates cleanly, logout does not, and short SP session lifetimes are the mitigation that actually works.
Failure modes and mitigations
- Signature not validated, or validated wrongly. The root of the entire vulnerability class. Validating against a certificate embedded in the assertion rather than the one from metadata is circular and accepts anything. Validating the signature on one element and then reading data from another is XML signature wrapping. Both have shipped in mainstream libraries.
- Missing audience restriction check. If you do not confirm the assertion names your entityID, any assertion the IdP ever signed for any SP is a valid login to yours. A malicious operator of a different SP in the same federation becomes an authentication bypass.
- Clock skew. Assertion windows are minutes wide. An SP whose clock has drifted rejects every assertion as expired or not-yet-valid, and the error surfaces as an unexplained login failure for all users at once. NTP is authentication infrastructure here, exactly as it is in Kerberos.
- Certificate expiry. IdP signing certificates expire, typically on a multi-year cycle, which guarantees nobody remembers. Every login fails simultaneously. Consuming metadata from a URL on a schedule turns this into a non-event; hand-pasted certificates turn it into an outage.
- Assertion replay. The browser holds the assertion and can present it twice. Without a replay cache keyed on assertion ID and retained for the validity window, a captured POST body is a reusable credential for as long as the window lasts.
- Relay state as an open redirect. If relay state is a URL and the SP redirects to it after login, an attacker who can initiate the flow gets a redirect off a trusted domain, post-authentication. Make relay state an opaque key into a server-side map.
- Deprovisioning lag. Disabling the IdP account stops new logins; it does not touch existing SP sessions. A terminated employee with a live cookie keeps access until it expires. Long SP sessions turn the central-offboarding guarantee into a fiction.
Operational playbook
- Use a maintained library; never hand-roll XML signature validation. Canonicalisation, namespace handling, and wrapping defences are genuinely hard and have defeated experts. This is the clearest 'do not write it yourself' in applied cryptography.
- Consume metadata from a URL, refreshed on a schedule. This makes certificate rotation automatic and turns the most common production incident into something you never see. Alert when the fetch fails, not when logins do.
- Alert on certificate expiry well ahead of it. Even with metadata refresh, know the expiry dates of every IdP you federate with and page someone at thirty days. The failure is total and simultaneous.
- Monitor clock offset on SP hosts as a security metric. It belongs on the same dashboard as authentication failures, because it causes them and nothing in the error message will tell you so.
- Keep SP sessions short and re-federate. This is the only lever that makes central deprovisioning real. An eight-hour session means eight hours of access after termination; an hour means an hour.
- Log every assertion validation with the assertion ID and every check's outcome. When an integration breaks — and it will break at the IdP end, in a system you cannot see — this log is the only evidence that distinguishes 'they changed the attribute release' from 'our clock is wrong'.
- Test with a real IdP before you ship. Every IdP interprets the specification slightly differently, especially around NameID formats and attribute naming. An integration that works against a test IdP and not against the customer's is the normal case, not the exception.