Skip to content

SAML Authentication — Complete Enterprise SSO Guide

DodaTech Updated 2026-06-28 5 min read

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

  1. Not signing SAML requests and assertions allows attackers to modify or forge authentication data.
  2. Failing to validate the NotBefore and NotOnOrAfter timestamps allows replay attacks with expired assertions.
  3. Not verifying the Audience restriction allows assertions meant for other services to be reused.
  4. Using HTTP binding instead of POST binding exposes the SAML response in URL parameters.
  5. Not handling SAML logout properly can leave sessions active in the service provider.
  6. Storing SAML certificates in source code instead of secure secret storage.

Practice Questions

  1. 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.

  1. 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.

  1. 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.

  1. 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

Is SAML being replaced by OIDC?

OIDC is simpler and better suited for modern applications, but SAML remains deeply entrenched in enterprise environments. Many organizations support both (SAML for enterprise SSO, OIDC for consumer identity).

What is SAML metadata?

SAML metadata is an XML document that describes a provider's capabilities, endpoints, certificates, and supported bindings. SP and IdP exchange metadata to configure trust.

How do I debug SAML issues?

Use SAML tracer browser extensions to see the SAML request and response. Validate assertions manually. Check certificate validity and clock synchronization (SAML is time-sensitive).

Can I use SAML without an enterprise IdP?

You need an identity provider. Options include: Okta, Azure AD, OneLogin, Keycloak (self-hosted). Some IdPs offer free developer tiers for testing.

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