SAML 2.0 has two distinct security mechanisms that are frequently confused: signing and encryption. Signing proves that the assertion was issued by the expected identity provider and has not been tampered with. Encryption ensures that the assertion's contents are only readable by the intended service provider. They protect against different threats, and most production SAML deployments should use both — but many use only signing and wonder why security teams ask about encryption during enterprise procurement.
What signing protects
A signed SAML response or assertion carries an XML digital signature that can be verified against the IdP's public certificate. This protects against:
- Assertion forgery: an attacker cannot create a valid assertion without the IdP's private key.
- Tampering: modifying any part of the signed content invalidates the signature.
- Response vs assertion signing: the response can be signed, the assertion can be signed, or both. Signing the assertion is generally more important because it is the specific element being trusted by the SP.
What signing does not protect: the content of the assertion is still plaintext in the SAML response. Anyone who intercepts the base64-encoded response can decode it and read the user's email address, group memberships, and any other attributes. SAML responses typically travel over HTTPS, so interception is unlikely — but not impossible in enterprise network environments with TLS inspection proxies.
What encryption protects
An encrypted SAML assertion is wrapped in an EncryptedAssertion element using the service provider's public key. Only the SP can decrypt it using its private key. This protects against:
- Attribute disclosure to intermediaries (IdP proxies, network monitoring)
- Log scraping: if SAML responses are logged (a bad practice, but it happens), encrypted assertions prevent attribute disclosure from those logs
- Compliance requirements: some regulations require protecting PII in transit even at the application layer, beyond TLS
SAML metadata configuration
<!-- SP metadata: declare signing cert for signature validation,
encryption cert for assertion encryption -->
<SPSSODescriptor
AuthnRequestsSigned="true"
WantAssertionsSigned="true">
<!-- Certificate for validating signed AuthnRequests from this SP -->
<KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIBkTCB+... (SP signing cert)</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<!-- Certificate for encrypting assertions TO this SP -->
<KeyDescriptor use="encryption">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIBkTCB+... (SP encryption cert)</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
</KeyDescriptor>
</SPSSODescriptor>
Implementing SAML in Node.js with samlify
import { IdentityProvider, ServiceProvider } from 'samlify';
import * as validator from '@authenio/samlify-node-xmllint';
samlify.setSchemaValidator(validator);
// SP configuration
const sp = ServiceProvider({
entityID: 'https://yourapp.com/saml/metadata',
assertionConsumerService: [{
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
location: 'https://yourapp.com/saml/acs',
}],
signingCert: fs.readFileSync('./certs/sp-signing.crt'),
privateKey: fs.readFileSync('./certs/sp-signing.key'),
encPrivateKey: fs.readFileSync('./certs/sp-encryption.key'), // for decrypting EncryptedAssertions
encCert: fs.readFileSync('./certs/sp-encryption.crt'),
wantAssertionsSigned: true,
wantMessageSigned: false, // response signing optional if assertion signed
});
// IdP configuration (loaded from IdP metadata or manual config)
const idp = IdentityProvider({
entityID: 'https://idp.customer.com/saml/metadata',
singleSignOnService: [{
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
location: 'https://idp.customer.com/saml/sso',
}],
signingCert: idpSigningCert, // IdP's cert for verifying assertion signatures
});
// ACS handler: validate and parse the SAML response
app.post('/saml/acs', async (req, res) => {
try {
const { extract } = await sp.parseLoginResponse(idp, 'post', {
body: req.body,
});
const email = extract.attributes.email || extract.attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'];
const nameId = extract.nameID;
// Find or create user, issue session
const user = await findOrCreateSSOUser(email, nameId, req.params.orgId);
await issueSession(res, user);
} catch (err) {
console.error('SAML ACS error:', err.message);
res.redirect('/login?error=saml_failed');
}
});
Key rotation for SAML partnerships
SAML certificates have fixed validity periods, typically 2–5 years. Rotating them requires coordinating with every SP or IdP that has your certificate configured in their metadata. The process:
- Generate a new certificate and key pair.
- Add the new certificate to your metadata alongside the old one — both listed in
KeyDescriptorelements. - Notify all partners of the upcoming rotation and provide a deadline.
- Partners update their configuration to trust the new certificate.
- Switch signing to use the new key (both certificates still accepted for verification).
- After the deadline, remove the old certificate from metadata.
The transition window (step 3 to step 6) should be at least 30 days for enterprise customers who may have slow change management processes. Automated metadata refresh helps: if both sides consume each other's metadata URL rather than a static file, updates propagate automatically when the metadata is updated.
Debugging signed/encrypted SAML
# Decode a base64 SAMLResponse for inspection (use in dev/debug only) # Never log raw SAMLResponse in production if assertion encryption is your threat model echo "$SAML_RESPONSE_BASE64" | base64 -d | xmllint --format - 2>/dev/null # Validate an XML signature openssl dgst -sha256 -verify idp-signing.pem \ -signature assertion-signature.bin \ assertion-canonical.xml # Check certificate validity openssl x509 -in idp-signing.crt -noout -dates -subject # Useful fields: notBefore, notAfter, subject CN (should match IdP entityID)
The most common SAML integration failures in practice are: clock skew (SAML assertions have a strict time window; if the SP and IdP clocks differ by more than 5 minutes, assertions will be rejected), certificate mismatch (wrong cert configured on either side), and attribute mapping errors (the attribute name the IdP uses for email differs from what the SP expects). Use a SAML debugging tool like SAML Tracer (browser extension) or samltool.com to inspect the decoded assertion during development.