SAML security checklist for SaaS developers
A code-level checklist for auditing your SAML service provider implementation: XML parser configuration, signature validation, assertion extraction, replay prevention, and testing.
This checklist is for developers who have built or are reviewing a homegrown SAML service provider implementation. It focuses on the code and configuration level: the specific things to verify in your parser setup, validation logic, and assertion extraction code, rather than the architectural decisions covered in our SAML security best practices guide.
Most SAML vulnerabilities disclosed in the past two years (ruby-saml, authentik, OneUptime, and others) weren't caused by exotic attacks. They came from implementers making subtly wrong choices in exactly the areas this checklist covers. Run through it before shipping a SAML integration, and again before any security review.
1. XML parser configuration
Disable DTD processing and external entities
SAML responses are XML documents, and XML parsers that process document type definitions (DTDs) or external entities are vulnerable to XXE injection. An attacker who can influence the SAML response can use a crafted DTD to read arbitrary files from your server or perform server-side request forgery.
The fix is a single configuration change on your XML parser. Verify it is in place:
Java (DocumentBuilderFactory)
Python (lxml)
Node.js (fast-xml-parser)
.NET
Check: does your parser configuration explicitly disable DTD processing? Do not rely on library defaults, which can change across versions.
Use one parser for the entire response
A SAML response should be parsed once, by a single parser instance, and that same parsed document should be used for both signature verification and assertion extraction. The OneUptime vulnerability disclosed in April 2026 showed exactly what happens when this rule is broken: isSignatureValid() verified the first <Signature> element using xml-crypto, while getEmail() always read from assertion[0] via xml2js. Two different parsers, two different views of the same document, one exploitable gap.
If your code does something like this, it is vulnerable:
The correct pattern:
Check: search your codebase for every location that parses the raw SAML response string. There should be exactly one.
2. Signature validation
Validate that a signature is present before doing anything else
An unsigned assertion should be rejected immediately, before any other processing. Do not fall through to a "signature optional" mode or log a warning and continue. The check should look like this:
Check: is there code anywhere that processes an assertion even when no signature is found?
Validate the signature using an absolute XPath expression
XSW attacks exploit the gap between what gets signed and what gets read: a valid signature validates one element, but the application reads authentication attributes from a different, attacker-controlled element. The most common way this gap appears is by locating the signature with a relative or position-based XPath expression that matches the first element with a given tag name, rather than the specifically signed element.
Do not locate the signature or the assertion like this:
Use absolute paths instead:
Check: review every XPath expression or DOM traversal used to locate the <Assertion> and <Signature> elements. None of them should use relative paths or getElementsByTagName.
Verify the Reference URI matches the assertion ID
The <ds:Reference URI> inside <ds:SignedInfo> tells you which element the signature covers. It should match the ID attribute on the assertion you extracted. If it doesn't, you may be validating a signature that covers a different element than the one you're about to trust.
Check: does your validation code explicitly compare the Reference URI to the assertion ID?
Validate signatures at the right level
The authentik vulnerability disclosed in 2025 was a textbook example of checking at only one level: the SAML spec allows signing at either the response level or the assertion level, or both, and implementations that check only one layer leave an opening for injection at the other.
Verify your implementation handles all three cases:
- Response signed, assertion unsigned
- Response unsigned, assertion signed
- Both signed
At minimum, require the assertion to be signed. If only the response envelope is signed and you accept unsigned assertions inside it, an attacker can potentially swap the assertion.
Check: write a test that sends a valid signed response containing an unsigned assertion with modified attributes. Your implementation should reject it.
Reject deprecated signing algorithms
SHA-1 is deprecated for cryptographic use. Your validation code should maintain an allowlist of accepted algorithms and reject anything not on it:
Check: can an IdP signed with rsa-sha1 successfully authenticate against your application?
3. Assertion field validation
Each of the following fields must be checked on every incoming assertion, in this order, before you create a session. A gap in any one of them is a potential authentication bypass.
Issuer
The <saml:Issuer> value must exactly match the entity ID you have stored for this SSO connection. Compare as a string, not as a URL (trailing slashes and case differences matter).
Audience restriction
The <saml:AudienceRestriction> must contain your application's entity ID. Without this check, an assertion issued for a different service provider could be accepted by yours.
Destination
The Destination attribute on the <samlp:Response> must match the ACS URL that received the POST request, including scheme, host, port, and path.
NotBefore and NotOnOrAfter
Assertions are time-bounded. Check both timestamps against the current time, with a small clock skew tolerance. Five minutes is the common default; two minutes is stricter and more secure.
InResponseTo (SP-initiated flows)
For SP-initiated SSO, the InResponseTo attribute on the response must match the ID of an AuthnRequest your application actually sent. If there's no match, the response is unsolicited.
Check: can an assertion with a fabricated or missing InResponseTo successfully log someone in?
4. Replay prevention
Assertion IDs must be tracked and deduplicated. Any replayed assertion whose NotOnOrAfter has not yet passed should be rejected.
The cache must be shared across all application instances. A per-process in-memory map doesn't work in a load-balanced environment.
Check: submit the same raw SAML response twice to your ACS endpoint. The second submission should be rejected, not processed.
5. Certificate and metadata handling
Pin certificates per connection
The certificate used to verify an assertion's signature must be the certificate associated with that specific SSO connection, not a global trusted certificate. One customer's IdP should not be able to sign an assertion that your application trusts for a different customer's connection.
Check: does your verification code look up the certificate from the connection record before validating, or does it use a global certificate store?
Verify the certificate chain
Do not accept a certificate embedded in the SAML response itself as the trust anchor. Only trust certificates you have previously stored when the connection was configured. If the response contains an <ds:X509Certificate> element, you can use it to identify which stored certificate to use, but you must verify the signature against your stored copy, not the one in the response.
Handle multiple certificates during rotation
IdPs publish both their current and upcoming certificate in metadata during rotation windows. Your implementation should accept a signature verified by any certificate currently in the connection's trust store, not just the first one.
Check: if you manually rotate an IdP certificate without updating your application's stored certificate first, does SSO break immediately? It should work during the overlap window.
6. Test assertions to run
Before shipping any SAML integration, run all of the following as automated tests. Each test submits a crafted assertion to your ACS endpoint and asserts that it is rejected with an authentication error, not accepted.
If any of these tests pass when they should fail, you have a validation gap. The last one is the most commonly missed: submit a response containing two assertion elements, where the second is signed correctly and the first contains escalated attributes. Your implementation should not authenticate the user as the identity in the first assertion.
When not to do this yourself
Building a secure SAML SP implementation requires getting every item on this checklist right, keeping up with SAML library CVEs, and maintaining the code as the threat landscape evolves. If your core product is not identity infrastructure, this is a significant ongoing commitment.
WorkOS handles signature validation, XSW prevention, XML parser hardening, replay detection, certificate rotation, and assertion field validation as part of the SSO platform, so your application receives a normalized user profile rather than raw SAML XML. See the WorkOS SSO documentation for details.