Engineering Atlas › System Design Fundamentals

SAML for enterprise sign-in

12 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

Roles and setup

The IdP (Okta, Entra ID, ADFS, Google Workspace) owns user accounts and authentication, including MFA. The SP is your application. Setup exchanges metadata: the SP gives its entity id and Assertion Consumer Service (ACS) URL; the IdP gives its entity id, SSO URL, and X.509 signing certificate.

SP-initiated flow

  1. The user visits the app. The SP creates an AuthnRequest with a unique id and redirects the browser to the IdP.
  2. The IdP authenticates the user, or reuses its own session.
  3. The IdP builds a Response containing an Assertion: subject (NameID), attributes (email, groups), conditions (NotBefore, NotOnOrAfter, Audience), and InResponseTo set to the request id. It signs the assertion, the response, or both.
  4. The browser POSTs the Response to the ACS URL via an auto-submitting HTML form.
  5. The SP validates it and creates its own session.

IdP-initiated flow skips step 1: the user clicks the app tile in the IdP portal. With no request id to match, it is more exposed to injection and replay, so many SPs restrict or disable it.

Validation checklist

Provisioning and logout

SAML covers login only. Accounts come from SCIM provisioning or just-in-time creation. Single logout is fragile, so bound SP sessions independently.

Down the stack

Layer 1The contract

The SP's contract is narrow: log a user in only from an assertion that is signed by the configured IdP key, addressed to this SP (Audience) and this endpoint (Recipient), currently within its validity window, never used before, and, for SP-initiated logins, answering a request this SP actually sent. The IdP's side of the contract is that it authenticated the subject at AuthnInstant using the method named in AuthnContextClassRef, such as password plus MFA. SAML says nothing about how long the SP's own session lasts. A bearer assertion is like cash: whoever holds it can present it, which is why NIST allows bearer assertions only up to its second federation level and requires holder-of-key or a bound authenticator at the top level (NIST SP 800-63C-4).

Layer 2The mechanism

The AuthnRequest usually travels with the HTTP-Redirect binding: the XML is compressed with raw DEFLATE (RFC 1951), base64-encoded, URL-encoded into a SAMLRequest parameter, and optionally signed with SigAlg and Signature query parameters. The Response comes back with the HTTP-POST binding as a base64 SAMLResponse form field plus RelayState. The signature is an enveloped XML-DSig ds:Signature element. Its Reference URI="#_abc" points at the signed element's ID, its transforms are enveloped-signature and exclusive canonicalisation, its DigestValue is a SHA-256 of the canonicalised element, and its SignatureValue signs the canonicalised SignedInfo. A SubjectConfirmation with method bearer carries Recipient, NotOnOrAfter, and InResponseTo. Signature wrapping works because the verifier resolves the signed element by ID while the application finds the assertion by a different path, such as the first Assertion element in the document. Somorovsky and colleagues found this gap exploitable in 11 of 14 major SAML frameworks in 2012 (USENIX Security 2012). A subtler variant is a parser differential: canonicalisation drops XML comments, so alice@corp.com<!---->.evil.com is signed as alice@corp.com.evil.com, but a library that reads only the first text node sees alice@corp.com (CERT VU#475445). The fix in every case is the same: read identity only from the exact bytes the signature covered.

Layer 3The machine

A signed response is typically 5 to 15 KB of XML, a third larger once base64-encoded into the POST body. Redirect-binding URLs must stay under server limits, often 8 KB, which is why requests are deflated. Canonicalisation builds a DOM, resolves namespaces, and reorders attributes, so it costs far more CPU and allocation than the RSA verification itself, which takes around 30 microseconds for a 2048-bit key with public exponent 65537. XML parsers are an attack surface: nested entity definitions ten levels deep with ten references each expand to a billion entities and exhaust memory, and external entities can read local files, so DTD processing must be disabled. The ACS POST arrives cross-site from the IdP's domain, so a pre-login cookie set with SameSite=Lax is not sent with it; SPs correlate the response through RelayState or a cookie marked SameSite=None; Secure. RelayState must not exceed 80 bytes (SAML 2.0 Bindings), so store an opaque key that maps to the return URL server-side rather than the URL itself, which also avoids an open redirect.

Why it's built this way

Enterprise IdPs often sit behind firewalls the SaaS cannot reach, so the browser carries every message. XML was the enterprise standard when SAML 2.0 became an OASIS Standard in March 2005 (OASIS technical overview), and XML has many equivalent byte representations, so signatures need canonicalisation. Bearer assertions can be intercepted, so they are short-lived and single-use. Signing parts of a document allows flexible processing but creates the gap that wrapping attacks exploit.

Diagram

sequenceDiagram
  participant B as Browser
  participant SP as App (SP)
  participant IdP
  B->>SP: GET /dashboard
  SP-->>B: redirect with AuthnRequest (id=r1)
  B->>IdP: AuthnRequest
  IdP->>B: login + MFA if no session
  IdP-->>B: HTML form with signed Response
  B->>SP: POST /acs (SAMLResponse)
  SP->>SP: verify signature, audience, time, InResponseTo=r1, replay
  SP-->>B: session cookie, redirect to /dashboard

Worked example

A B2B SaaS sells to 1,500 enterprise customers, each with its own IdP. Peak SSO logins are 300 per second at 9 a.m. across time zones.

Per-tenant configuration: each customer has an IdP entity id, SSO URL, and one or two signing certificates. 1,500 tenants x 2 certificates is 3,000 certificates in a small table, cached in memory.

Validation cost: parsing a 10 KB response and verifying an RSA-2048 signature takes 1 to 2 ms of CPU, under one core at 300 logins per second.

Replay cache: assertion ids are kept until their NotOnOrAfter, typically 5 minutes. 300 per second x 300 seconds = 90,000 entries, a few MB in a shared store such as Redis with TTLs. It must be shared across SP instances, or an attacker replays to a different instance.

Certificate expiry: with 1,500 IdPs rotating certificates every 1 to 3 years, 2 to 4 rotations happen per working day. Track each expiry, notify admins 30 days ahead, accept a second certificate during rollover, and poll IdP metadata URLs where available. Entra ID, for example, issues SAML signing certificates valid for three years by default, emails admins 60, 30 and 7 days before expiry, and asks SaaS vendors to hold a primary and a secondary certificate and re-read each tenant's metadata at least every 24 hours (Microsoft Learn). At 1,500 tenants that is 1,500 metadata fetches a day, trivial next to the login traffic.

In the wild

Trade-offs

OptionStrengthsWeaknessesChoose when
SAML 2.0Universal enterprise IdP support, matureXML signature complexity, browser-only flowsEnterprise workforce SSO
OpenID ConnectJSON and JWT, works for mobile and APIs, simplerSome legacy enterprise IdPs lack itNew apps, consumer login, mobile
SSO broker serviceOne integration, many IdPs handled for youVendor dependency, cost per connectionMany enterprise customers, small team
Build on a SAML library in-houseFull control, no per-connection feesYou own every validation bug and support ticketStrong security team, unusual needs

Pitfalls in production

Leadership lens

Check yourself

  1. Why must the SP check that InResponseTo matches a request it actually sent?

    Show answer

    It ties the response to a login this browser started, which blocks unsolicited assertions and some replay and login-CSRF attacks.

  2. How does an XML signature wrapping attack work?

    Show answer

    The attacker keeps a validly signed assertion in the document and adds a forged unsigned one. If the SP verifies the signature on one element but reads identity from another, the forged identity is accepted.

  3. Why is the Audience check necessary even with a valid signature?

    Show answer

    The IdP signs assertions for many SPs. Without the audience check, an assertion issued for another app could log the user into yours.

  4. Why should account linking use a stable NameID instead of email?

    Show answer

    Emails can be reassigned or edited by IdP admins, letting one person's assertion map to another person's account.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →