SAML Authentication — Complete Enterprise SSO Guide
In this tutorial, you will learn about SAML Authentication. We cover key concepts, practical examples, and best practices to help you master this topic.
SAML (Security Assertion Markup Language) is an XML-based federated authentication standard that enables single sign-on across enterprise applications by exchanging authentication and authorization data between an identity provider and a service provider.
What You'll Learn
By the end of this lesson, you will understand SAML 2.0 flow, configure SAML SSO for enterprise applications, implement SAML SP-initiated and IdP-initiated SSO, and compare SAML with OIDC for enterprise use cases.
Why It Matters
SAML is the dominant SSO protocol for enterprise environments, supported by Microsoft Entra ID, Okta, OneLogin, and most identity platforms. Durga Antivirus Pro uses SAML for enterprise customer SSO, allowing companies to authenticate using their existing corporate identity provider.
Real-World Use
An employee accesses their company's expense reporting tool. The tool redirects them to the company's Okta portal. After entering corporate credentials (and MFA if required), Okta sends a SAML assertion back to the expense tool. The employee is logged in without creating a separate account.
SAML SSO Flow
sequenceDiagram
participant User
participant SP as Service Provider (App)
participant IdP as Identity Provider (Okta/Azure AD)
User->>SP: Access protected resource
SP->>User: Redirect to IdP with SAML Request
User->>IdP: Authenticate (password + MFA)
IdP->>User: Redirect to SP with SAML Response
User->>SP: POST SAML Response
SP->>SP: Validate SAML Assertion (signature, audience, expiry)
SP->>User: Authenticated session
SAML SP Implementation (Python)
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.settings import OneLogin_Saml2_Settings
import requests
class SAMLAuth:
def __init__(self, saml_settings_path="saml_settings.json"):
self.settings_path = saml_settings_path
def prepare_request(self, request):
return {
"http_host": request.host,
"server_port": request.port,
"script_name": request.path,
"get_data": request.GET,
"post_data": request.POST,
"https": "on" if request.scheme == "https" else "off",
}
def initiate_sso(self, request):
req = self.prepare_request(request)
auth = OneLogin_Saml2_Auth(req, custom_base_path=self.settings_path)
return auth.login()
def handle_acs(self, request):
req = self.prepare_request(request)
auth = OneLogin_Saml2_Auth(req, custom_base_path=self.settings_path)
auth.process_response()
if auth.get_errors():
print(f"SAML error: {auth.get_errors()}")
return None
if not auth.is_authenticated():
print("SAML authentication failed")
return None
attributes = auth.get_attributes()
name_id = auth.get_nameid()
print(f"[SAML] Authenticated user: {name_id}")
print(f"[SAML] Attributes: {attributes}")
session_data = {
"name_id": name_id,
"attributes": attributes,
"session_index": auth.get_session_index(),
}
return session_data
def initiate_slo(self, request):
req = self.prepare_request(request)
auth = OneLogin_Saml2_Auth(req, custom_base_path=self.settings_path)
return auth.logout()
SAML Settings Configuration
{
"strict": true,
"debug": false,
"sp": {
"entityId": "https://myapp.example.com/metadata",
"assertionConsumerService": {
"url": "https://myapp.example.com/saml/acs",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
},
"singleLogoutService": {
"url": "https://myapp.example.com/saml/logout",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
},
"idp": {
"entityId": "https://company.okta.com",
"singleSignOnService": {
"url": "https://company.okta.com/sso/saml",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"singleLogoutService": {
"url": "https://company.okta.com/slo/saml",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"x509cert": "MIID..."
},
"security": {
"authnRequestsSigned": true,
"wantAssertionsSigned": true,
"wantAssertionsEncrypted": false,
"wantNameIdEncrypted": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
}
}
SAML Assertion Validation
def validate_saml_assertion(assertion_xml, idp_cert):
from signxml import XMLVerifier
from lxml import etree
root = etree.fromstring(assertion_xml.encode())
try:
verified = XMLVerifier().verify(root, x509_cert=idp_cert)
print("[SAML] Assertion signature verified")
except Exception as e:
print(f"[SAML] Signature verification failed: {e}")
return False
conditions = root.find(".//{urn:oasis:names:tc:SAML:2.0:assertion}Conditions")
if conditions is not None:
not_before = conditions.get("NotBefore")
not_on_or_after = conditions.get("NotOnOrAfter")
from datetime import datetime
now = datetime.utcnow()
if not_before and now < datetime.fromisoformat(not_before.replace("Z", "+00:00")):
print("[SAML] Assertion not yet valid")
return False
if not_on_or_after and now > datetime.fromisoformat(not_on_or_after.replace("Z", "+00:00")):
print("[SAML] Assertion expired")
return False
audience = root.find(".//{urn:oasis:names:tc:SAML:2.0:assertion}Audience")
if audience is not None and audience.text != "https://myapp.example.com/metadata":
print(f"[SAML] Invalid audience: {audience.text}")
return False
print("[SAML] All validations passed")
return True
Common Mistakes
- Not signing SAML requests and assertions allows attackers to modify or forge authentication data.
- Failing to validate the NotBefore and NotOnOrAfter timestamps allows replay attacks with expired assertions.
- Not verifying the Audience restriction allows assertions meant for other services to be reused.
- Using HTTP binding instead of POST binding exposes the SAML response in URL parameters.
- Not handling SAML logout properly can leave sessions active in the service provider.
- Storing SAML certificates in source code instead of secure secret storage.
Practice Questions
- What is the difference between IdP-initiated and SP-initiated SAML SSO?
SP-initiated: User accesses the application first, which redirects to the IdP for authentication. IdP-initiated: User starts at the IdP portal and clicks on the application icon to launch it.
- Why does SAML use XML signatures instead of JWT?
SAML predates JWT and was designed when XML signatures were the standard for document-level signing. OIDC/JWT is newer and simpler. SAML remains dominant in enterprise because of existing IdP support.
- What is the purpose of the NameID in SAML?
The NameID is a persistent identifier for the user, typically their email address or a federated ID. It is used by the service provider to identify the user across sessions.
- Challenge: Set up a SAML integration between a simple Python application and a SAML test IdP (like samltest.id), including metadata exchange, SP-initiated SSO, attribute mapping, and SLO.
FAQ
Mini Project: SAML Metadata Validator
Build a CLI tool that parses and validates SAML metadata XML files, checking certificate expiry, endpoint URLs, supported bindings, and compatibility with SAML 2.0 specification.
import sys
from lxml import etree
from datetime import datetime
def validate_saml_metadata(xml_path):
tree = etree.parse(xml_path)
root = tree.getroot()
ns = {
"md": "urn:oasis:names:tc:SAML:2.0:metadata",
"ds": "http://www.w3.org/2000/09/xmldsig#",
}
entity_id = root.get("entityID")
print(f"Entity ID: {entity_id}")
sso_services = root.findall(".//md:SingleSignOnService", ns)
for sso in sso_services:
binding = sso.get("Binding")
location = sso.get("Location")
print(f"SSO: {binding.split(':')[-1]} -> {location}")
certs = root.findall(".//ds:X509Certificate", ns)
for cert in certs:
print(f"Certificate: {cert.text[:40]}...")
valid_until = root.get("validUntil")
if valid_until:
expiry = datetime.fromisoformat(valid_until.replace("Z", "+00:00"))
remaining = (expiry - datetime.utcnow()).days
print(f"Valid until: {valid_until} ({remaining} days remaining)")
print("Metadata validation complete")
if __name__ == "__main__":
validate_saml_metadata(sys.argv[1])
What's Next
Compare SAML vs OAuth to choose the right protocol for your use case, then explore API key authentication for service-to-service communication.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro