SAML (Security Assertion Markup Language)
An XML-based protocol for single sign-on (SSO) between an identity provider (IdP) and a service provider (SP). SAML allows users to log in once to the IdP and access multiple services without having to re-enter their password.
SAML 2.0 is the de facto standard for enterprise SSO. When you log in to Slack, Salesforce, GitHub Enterprise, or SAP using your corporate credentials, SAML is usually running in the background. The protocol is powerful but also complex—and thus vulnerable to weaknesses such as XML signature wrapping.
Basic SAML Concepts
The Three Roles
1. Principal (User):
- Wants to access a service
2. Identity Provider (IdP):
- Knows and authenticates the user
- Issues a SAML assertion (certificate confirming that User XY exists)
- Examples: Microsoft Entra ID (Azure AD), Okta, JumpCloud, Active Directory Federation Services (ADFS)
3. Service Provider (SP):
- Service the user wishes to access
- Trusts the IdP (via prior metadata exchange)
- Examples: Salesforce, Jira, GitHub Enterprise, AWS
Trust Between IdP and SP
- Prerequisite: Metadata exchange (EntityID, certificates, endpoints)
- IdP certificate: SP only trusts assertions signed with this key
- EntityID: unique identifier (often a URL)
SAML SSO Flow (SP-initiated)
Process when a user accesses the service provider:
1. User navigates to: https://app.salesforce.com/login
2. SP detects: User not logged in → SAML required
3. SP generates AuthnRequest (XML):
<samlp:AuthnRequest
ID="_abc123"
AssertionConsumerServiceURL="https://salesforce.com/saml/callback"
Destination="https://idp.firma.de/sso/saml"
/>
4. SP redirects user to IdP (GET/POST with AuthnRequest)
5. IdP displays login page (or uses existing session)
6. User logs in to the IdP (password + MFA)
7. IdP creates SAML response (XML) with assertion:
<Assertion>
<Subject>user@firma.de</Subject>
<AttributeStatement>
<Attribute Name="email">user@firma.de</Attribute>
<Attribute Name="role">admin</Attribute>
</AttributeStatement>
<Conditions NotBefore="..." NotOnOrAfter="..."/>
</Assertion>
8. IdP signs assertion with private key (XML Signature)
9. IdP sends response via browser (HTTP POST) to SP
10. SP verifies signature with IdP certificate
11. SP creates a session for the user → access granted
> Important: The browser acts as an intermediary! The IdP and SP never communicate directly.
SAML vs. OAuth 2.0 / OpenID Connect
| Aspect | SAML 2.0 | OAuth 2.0 + OIDC |
|---|---|---|
| Format | XML | JSON (JWT) |
| Purpose (primary) | Authentication | Authorization |
| OIDC support | - | Authentication |
| Adoption | Enterprise (Legacy) | Web/Mobile/APIs |
| Complexity | High (XML, certificates) | Medium (JSON) |
| Mobile Apps | Poor | Very good |
| Browser-only? | Yes (Redirect Flow) | No (API Flows) |
| Example IdPs | ADFS, Okta, Entra | Entra, Google, Auth0 |
When to use SAML:
- Enterprise applications (SAP, Salesforce, ServiceNow)
- Existing SAML ecosystem
- When the SP only supports SAML
When to use OAuth 2.0/OIDC:
- New development
- Mobile apps
- APIs and microservices
- Modern IdPs (Auth0, Cognito)
Hybrid scenario: Many enterprises use both—SAML for legacy enterprise apps, OIDC for new apps and APIs. Entra ID / Okta supports both.
SAML Configuration - Important Parameters
Service Provider Configuration (Example: Jira):
entityID: https://jira.firma.de # Unique SP ID
ACS URL: https://jira.firma.de/plugins/servlet/saml/sso
# Callback after SSO
SLO URL: https://jira.firma.de/plugins/servlet/saml/slo
# Single Logout
NameID Format: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
# Who is the user?
Signing Certificate: <zertifikat des="" idp=""> # For signature verification
Identity Provider Configuration (Example: Entra ID / Azure AD):
Identifier (EntityID): https://jira.firma.de
Reply URL (ACS): https://jira.firma.de/plugins/servlet/saml/sso
Sign-on URL: https://idp.firma.de/saml2/sso/request
Claims Mapping (Attributes):
email: user.mail
username: user.userprincipalname
firstName: user.givenname
lastName: user.surname
role: user.assignedroles
Signing Certificate:<privates zertifikat="" des="" idp="">
Security Parameters:
- Assertion valid for: max. 5 minutes (NotOnOrAfter!)
- Assertion Encryption: yes (if SP supports it)
- Request Signing: yes (SP signs AuthnRequest)
- Response Signing: Required (IdP signs Assertion)
SAML Security Vulnerabilities
1. XML Signature Wrapping (XSW)
The SAML response contains both signed and unsigned parts. An attacker inserts a forged assertion; the original signed assertion is "wrapped." Some SP implementations check the incorrect element.
Example CVE-2017-11427 (SimpleSAMLphp): Authentication as any user is possible. Root cause: incorrect XPath query for signed element.
Protection: Use up-to-date, patched SAML libraries.
2. XML External Entity (XXE) in SAML
SAML parsers read external XML entities.
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
Mitigation: Disable XXE in the XML parser.
3. Comment Injection / Parser Differentials
IdP and SP parse XML differently:
<!-- comment -->
user@firma.defake@attacker.de
→ IdP sees: user@firma.de
→ SP sees: user@firma.defake@attacker.de (or vice versa!)
4. Replay Attack
Old SAML assertion is reused. Protection: Cache InResponseTo + AssertionID, time window (5 min)
5. Open Redirect at IdP
RelayState parameter not validated → redirect to phishing site after login.
SAML Security Checklist
- Verify XML signatures correctly (entire element, not just content)
- Check time window (NotBefore, NotOnOrAfter)
- Cache Assertion ID (replay protection)
- Validate InResponseTo (AuthnRequest → Response mapping)
- Rotate IdP certificate (annually)
- Use the latest SAML library (check for CVEs!)
- SAML Tracer (Firefox extension) for debugging
SAML Troubleshooting
Common Errors and Solutions
"Audience Restriction" error:
- SP EntityID does not match the assertion
- Solution: Configure the EntityID identically in the IdP and SP
"Assertion is expired":
- Time difference between the IdP and SP servers
- Solution: Synchronize NTP on both systems; extend the assertion validity window if necessary (5→10 min)
"Signature validation failed":
- IdP certificate expired or not imported
- Solution: Import new certificate into SP
"NameID mismatch":
- Email in IdP: Case-sensitive
- Solution: Use case-insensitive comparison in SP
Debugging Tools
- SAML Tracer (Firefox/Chrome extension): displays Base64-decoded XML
- https://www.samltool.com/decode.php: decode SAML response
- SP logs: usually located under
/var/log/apache2/or in app logs