The problem
A company runs dozens of web applications, many of them SaaS: expenses, travel, HR, CRM. If every one keeps its own passwords, each needs its own multi-factor setup and password reset, phishers get dozens of login pages to imitate, and nobody remembers to close a leaver's accounts everywhere. The company wants one place where people sign in, under its own policies, and every application should trust that sign-in without ever seeing a password.
The applications and the identity provider usually belong to different organizations and live on different domains, so they can't share a cookie or a database. They need a standard message that one side can create, the other can verify, and the user's browser can carry between them. SAML 2.0, an OASIS Standard since March 2005, defines that message and the protocol around it, and it is still widely used for enterprise web single sign-on.
How it works
The parties. The identity provider (IdP) authenticates the user and vouches for them. The service provider (SP) is the application that relies on that statement. Each has an entity ID, a URI (typically a URL) that names it uniquely and appears as Issuer and Audience in messages. Before anyone signs in, the two exchange metadata: the SP's entity ID and its Assertion Consumer Service (ACS) URL, the endpoint that receives responses; and the IdP's entity ID, its single sign-on (SSO) URL and the certificate for the key that signs its assertions.
The assertion is the IdP's signed XML statement about the user:
Issuer: the IdP's entity ID.Signature: an enveloped XML signature with a single reference to the assertion's ownID(SAML Core §5.4).Subject: aNameIDthat identifies the user, plus a bearerSubjectConfirmationwhose data say where the assertion may be delivered (Recipient, the ACS URL), until when (NotOnOrAfter) and in answer to which request (InResponseTo).Conditions: a validity window (NotBefore,NotOnOrAfter) and anAudienceRestrictionthat names the SP's entity ID.AuthnStatement: when and how the user authenticated, and aSessionIndexthat logout refers to.AttributeStatement: attributes such as email, display name and group membership.
NameID formats (SAML Core §8.3) say what kind of identifier the subject is. A persistent identifier is an opaque pseudo-random value that the IdP generates for one service provider or group of them (a pairwise pseudonym of at most 256 characters), so two applications can't link the user through it. A transient identifier is temporary and not reused across sign-ins. emailAddress is convenient, but addresses change and get reassigned; unspecified leaves the choice to the IdP, and X.509 subject names, Windows domain names, Kerberos principals and entity identifiers are defined too. Microsoft Entra ID, for example, issues a pairwise value for persistent (and for unspecified) and a random value per sign-in for transient.
Bindings describe how a SAML message travels over HTTP (SAML Bindings):
- HTTP-Redirect deflates the XML, base64- and URL-encodes it, and puts it in a
SAMLRequest(orSAMLResponse) query parameter. A signature can't stay inside the XML; the sender signs the query string instead and addsSigAlgandSignatureparameters. It suits the smallAuthnRequest. - HTTP-POST puts the base64 XML in a hidden field of a form that submits itself. Responses use it: the Web Browser SSO profile forbids HTTP-Redirect for them, because a response is typically too long for a URL.
- HTTP-Artifact sends only a short reference through the browser; the receiver fetches the message over a back channel (typically SOAP) that must be mutually authenticated. It keeps the assertion out of the browser at the cost of a server-to-server call.
Profiles combine messages and bindings for a use case. The Web Browser SSO profile (SAML Profiles §4.1) is the flow in the diagram; others cover single logout, enhanced clients and proxies that talk to the IdP themselves (ECP) and artifact resolution.
SP-initiated sign-in (steps 1 to 3). Expense creates an <AuthnRequest> with a fresh ID, its entity ID as Issuer, its ACS URL and the binding it wants the answer on, keeps the ID in Ana's session, and redirects her browser to the IdP's SSO URL. The IdP has to check that the ACS URL really belongs to that SP (Profiles §4.1.4.1), or an attacker could have assertions delivered to a URL of their choosing. After authenticating Ana, the IdP returns a page that posts the SAMLResponse to the ACS URL. Expense validates it, maps the user and sets its own session cookie. When Travel sends Ana to the IdP later, the IdP's own session cookie spares her a second login: that is the single sign-on.
IdP-initiated sign-in (step 4). The profile also lets the IdP send an unsolicited response, for example when a user clicks an app tile on the IdP's portal (Profiles §4.1.5). It must not carry InResponseTo, it normally goes to the SP's default ACS endpoint, and the IdP may add a RelayState that, by prior agreement with the SP, names the page to open.
Validating a response. SAML Profiles §4.1.4.3 sets the rules and the OWASP SAML Security Cheat Sheet adds the hardening. Your library should do all of it, but you should know what it does:
- Parse defensively. Refuse DTDs and
DOCTYPEdeclarations (OWASP's XXE prevention guidance), and validate the document against local, trusted copies of the SAML schemas. - Check the response: a
Successstatus; aDestinationequal to the ACS URL it arrived at (mandatory when the response is signed); and anInResponseTothat matches a request this browser session still has outstanding, which you then mark as used. - Verify the signature with the key from the IdP's metadata, never with a certificate the message brings along in
KeyInfo. Accept only the algorithms you expect (OWASP asks for RSA-SHA256 or stronger and no SHA-1) and only the enveloped-signature and exclusive canonicalization transforms (Core §5.4.4). Make sure the reference points at the very element you go on to read. Issuerof the assertion equals the IdP's entity ID.- Subject confirmation: a bearer
SubjectConfirmationDatawhoseRecipientis your ACS URL, whoseNotOnOrAfterhasn't passed, and whoseInResponseTois your request ID (absent only if you accept unsolicited responses). - Conditions:
NotBefore≤ now <NotOnOrAfter, with a small allowance for clock skew; anAudienceequal to your entity ID; reject any condition you don't understand. - Replay: remember each accepted assertion
IDuntil itsNotOnOrAfterpasses and reject repeats (Profiles §4.1.4.5 requires this for the POST binding). - Read only what was signed: take
NameIDand attributes from the element the signature covers, located by its position, not by searching the document for the first<Assertion>orNameID. - Session limits: if the
AuthnStatementcarriesSessionNotOnOrAfter, end your session by then, and check the authentication context if the app needs MFA.
It is the same discipline as JWT validation, plus XML's extra ways to fail.
Metadata and certificate rollover. SAML metadata (an EntityDescriptor per party) lists the endpoints and their bindings, keys marked for signing or encryption, and flags such as the IdP's WantAuthnRequestsSigned and the SP's AuthnRequestsSigned and WantAssertionsSigned; validUntil and cacheDuration bound how long a copy stays good. Signing certificates expire. Microsoft Entra ID, for example, creates a self-signed certificate valid for three years for each SAML application and emails reminders 60, 30 and 7 days before it expires. A rollover without an outage publishes the new certificate first, lets each SP add it from the metadata as a second trusted key, switches signing to it, and only then retires the old one. Microsoft asks SaaS vendors to read each customer's per-application metadata URL, check it at least every 24 hours and keep a primary and a secondary signing certificate. An SP with a hand-pasted certificate rejects every sign-in from the moment the IdP switches.
When to use it
- Enterprise web applications and SaaS whose users sign in through a corporate IdP. Workforce IdPs such as Microsoft Entra ID and Okta document SAML integration for this.
- Applications that already speak SAML, or customers whose IdP offers nothing else. Microsoft's planning guidance is to choose OpenID Connect when the application supports it, and SAML for existing applications that don't use OpenID Connect or OAuth.
- Not for native mobile apps, single-page apps or APIs. The Web Browser SSO profile hands a server-side web app a signed statement through a browser POST and gives it nothing to call APIs with. OpenID Connect, on top of OAuth 2.0 Authorization Code + PKCE, covers those cases: a JSON ID token for the app, access tokens for APIs, and an authorization code redeemed over a back channel, so the tokens never sit in a URL or a form. Many IdPs speak both, and a federation broker can speak SAML to a customer's IdP and OpenID Connect to your app (see Federated Identity).
Trade-offs
- XML signatures are easy to get wrong. Canonicalization, IDs and references leave room for a parser to read something other than what was signed, and the attack history below is long. Use a maintained library and keep it patched.
- The assertion is a bearer credential in the front channel. Whoever holds it can present it until it expires, and it passes through the browser, its extensions and anything that logs the traffic. Short lifetimes, the
Recipient,Audienceand replay checks, and optionally encryption, limit the damage. - Configuration per application. Every SP and IdP pair exchanges metadata, agrees on a NameID format and attribute names, and has to survive certificate rollovers. Across hundreds of applications that is real operational work.
- Logout is weak. Ending one session rarely ends the others reliably (see below).
- Attributes are a snapshot. Group membership is what it was at sign-in. A removed group or a disabled account only shows up at the next sign-in, unless you also provision through SCIM or keep sessions short.
Implementation notes
- Use a maintained library, or a broker. Don't write XML signature verification yourself. Libraries with recent releases include Spring Security's SAML 2.0 support (Java), Sustainsys.Saml2 (.NET), pysaml2 (Python), node-saml (Node.js), ruby-saml (Ruby) and SimpleSAMLphp (PHP). Alternatively, let an identity broker speak SAML to your customers' IdPs and give your app OpenID Connect.
- Signed responses or signed assertions. With the POST binding every assertion must be protected by a signature, either on the assertion itself or on the whole response (SAML 2.0 Errata 05, E26). A response signature also covers
Destination,InResponseToand the status; an assertion signature stays verifiable if the assertion is later extracted and stored. Microsoft Entra ID signs the assertion by default for most gallery applications, can sign the response or both instead, and signs with SHA-256 unless an administrator switches an application that only supports SHA-1. SetWantAssertionsSignedin your metadata if you require signed assertions, and check that whichever signature you verify covers the data you use. - Encrypted assertions. SAML Bindings advise against the Redirect and POST bindings when the content must not be exposed to the browser. An
EncryptedAssertion(XML Encryption, with the key from the SP's metadata marked forencryption) keeps the attributes away from the browser and its extensions. Decrypt, then validate the signature exactly as before: encryption hides the content but proves nothing about who wrote it. Entra ID's SAML token encryption, a P1 or P2 feature, uses AES-256 and the public key of a certificate you upload. OWASP also suggests dropping the XML Encryption algorithms known to be insecure. - RelayState. It brings the user back to where they started. The binding caps it at 80 bytes, says it should be integrity-protected, and requires the IdP to return it unchanged. Keep the return URL on the server and send an opaque random key. If a portal sends a URL in
RelayStatefor IdP-initiated sign-in, accept only URLs on an allowlist, or it becomes an open redirect (OWASP). - IdP-initiated sign-in. An unsolicited response has nothing to match: no request ID, and no evidence that this browser started a sign-in. That removes a defence against replayed or injected assertions and against login CSRF, where an attacker signs the victim in to the attacker's account; OWASP calls the flow less secure by design and Auth0 recommends against it where SP-initiated is possible. NIST SP 800-63C-4 requires protection against assertion injection from FAL2 up, with the transaction starting at the relying party. The safer pattern is a portal tile that opens the application's login URL, so the application starts an SP-initiated sign-in: Entra ID's My Apps can do that when an application has a Sign on URL, and OpenID Connect defines the same idea as login initiated by a third party. If you must accept unsolicited responses, accept them only from IdPs you list, with the replay cache and short lifetimes.
- The request ID has to survive a cross-site POST. The response arrives as a top-level POST from the IdP's site, and browsers don't send
SameSite=LaxorStrictcookies on cross-site POSTs. If the outstandingAuthnRequestID lives in a cookie-backed session, that cookie needsSameSite=None; Secure, orInResponseTonever matches. - Attribute mapping and provisioning. Key users on the
NameIDfrom the issuing IdP (preferably persistent), not on an email address, and map groups to your own roles. IdPs name attributes their own way: Entra ID uses URIs such ashttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, and it leaves the groups claim out entirely when a user is in more than 150 groups. Just-in-time provisioning creates the local user at first sign-in; SCIM (RFC 7643, RFC 7644) creates users and groups ahead of time and removes leavers. Federated Identity covers both and the account lifecycle around them. - Single logout is unreliable. The Single Logout profile has the IdP send a
LogoutRequest(which must be signed on the Redirect and POST bindings) to every SP where the user has a session, identified bySessionIndex, either through the browser or over SOAP. Through the browser, one SP that is down or slow breaks the chain with an error page, and the SP that started the logout learns little about what failed; Shibboleth's notes on SLO conclude that the sensible advice after most failures is to close the browser. Over SOAP the SP never sees the user's cookie, so it must be able to find and end sessions byNameIDorSessionIndex. Keep application sessions short and treat single logout as best effort. - Known attack classes. Each of these is a reason to use a widely used, maintained library, update it promptly, and test it with tampered responses.
- Signature wrapping: the attacker moves the signed assertion aside and puts a forged one where the application looks, so the signature verifies but the application reads the forgery. A 2012 USENIX Security paper found critical wrapping flaws in 11 of 14 major SAML frameworks, Salesforce, Shibboleth and IBM XS40 among them.
- XML parser weaknesses: DTDs enable external entity (XXE) and entity-expansion attacks, so refuse them. Two parsers that disagree are just as dangerous: in March 2025 GitHub Security Lab showed that ruby-saml up to 1.17.0 (CVE-2025-25291 and CVE-2025-25292) used two XML parsers, REXML and Nokogiri, while verifying signatures, and that a document they read differently let anyone holding one valid signature from the IdP sign in as any user. GitLab was affected; ruby-saml 1.18.0 fixed it.
- Comments inside
NameID(2018): canonicalization usually leaves comments out of what is signed, so an attacker registered asuser@example.com.evil.examplecould insert<!---->afteruser@example.comwithout breaking the signature, and libraries that read only the first text node then sawuser@example.com. CERT/CC VU#475445 (February 2018), reported by Kelby Ludwig of Duo Security, lists affected implementations including python-saml, ruby-saml, saml2-js, OmniAuth-SAML and Shibboleth's OpenSAML C++.
- What the application still owns. SAML ends at sign-in. The application runs its own session (cookie flags, idle and absolute timeouts, never beyond
SessionNotOnOrAfter) and decides authorization itself, treating the mapped roles as input rather than as the decision; see Sessions vs Tokens and Policy-Based Authorization. - Testing and troubleshooting. A browser extension such as SAML-tracer (Firefox and Chrome) decodes every SAML message as it passes. Most failures are mismatched configuration: entity ID, ACS URL, certificate, NameID format or attribute names. Microsoft Entra ID, for example, reports
AADSTS50011when the reply (ACS) URL isn't registered andAADSTS700016when it can't find the identifier (entity ID). Check the clocks too: Entra ID setsNotBeforeto the moment of issue and adds no buffer for clock differences, so an SP whose clock runs slightly behind sees assertions that are not valid yet. Keep NTP running and the skew allowance small. Add negative tests: a broken signature, a wrong audience, an expired or replayed assertion, and an unsolicited response if you don't accept them must all be rejected.