Quickstart

This page walks through a complete Web Browser SSO exchange using the in-process helpers, so you can run it end to end without a network or a real IdP/SP.

Service Provider: process a response

Given the XML of a SAML Response received at your Assertion Consumer Service (ACS) endpoint, verify its signature and extract the authenticated identity:

from pygamlastan import xml, crypto, security, profiles

# 1. Verify the enveloped XML-DSig signature using the trusted IdP cert.
verifier = crypto.SamlVerifier.from_cert(idp_certificate_pem)
verified = verifier.verify_enveloped(response_xml)

# 2. Parse the response into owned Python objects.
response = xml.parse_response(response_xml)

# 3. Validate and extract identity. `verified_signed_ids` ties the
#    signed-assertion requirement to the cryptographic verification above.
result = profiles.process_response(
    response,
    security.SecurityConfig(),                 # production defaults
    sp_entity_id="https://sp.example.org/sp",
    acs_url="https://sp.example.org/acs",
    expected_idp_entity_id="https://idp.example.org",
    expected_request_id="_the_request_id",     # None for unsolicited
    verified_signed_ids=verified.signed_reference_ids(),
    replay_cache=security.InMemoryReplayCache(),
)

print(result.name_id)
print(result.attributes_dict())   # {"mail": ["alice@example.org"], ...}

Identity Provider: build a response

On the IdP side, turn an authenticated principal into a SAML Response:

from pygamlastan import core, profiles

options = profiles.ResponseOptions(
    idp_entity_id="https://idp.example.org",
    sp_entity_id="https://sp.example.org/sp",
    acs_url="https://sp.example.org/acs",
    in_response_to="_the_request_id",
    authn_context_class_ref=core.AUTHN_CONTEXT_PASSWORD,
    attributes=[
        core.Attribute("mail", values=["alice@example.org"]),
        core.Attribute("displayName", values=["Alice"]),
    ],
)
name_id = core.NameId("alice@example.org", format=core.NAMEID_TRANSIENT)
response = profiles.create_response(options, name_id)

unsigned_xml = response.to_xml()   # next: sign it (see the signing guide)

A full round trip

Putting both sides together, with permissive validation so the example does not require real signatures:

from datetime import datetime, timezone
from pygamlastan import core, xml, profiles, security

now = datetime(2026, 6, 25, 10, 0, 0, tzinfo=timezone.utc)
IDP, SP, ACS = "https://idp.example.org", "https://sp.example.org/sp", "https://sp.example.org/acs"

# IdP issues a response for request "_req1".
options = profiles.ResponseOptions(
    IDP, SP, ACS, in_response_to="_req1",
    authn_context_class_ref=core.AUTHN_CONTEXT_PASSWORD,
    attributes=[core.Attribute("mail", values=["alice@example.org"])],
)
response = profiles.create_response(options, core.NameId("alice", format=core.NAMEID_TRANSIENT), now=now)

# SP consumes it.
parsed = xml.parse_response(response.to_xml())
result = profiles.process_response(
    parsed, security.SecurityConfig.permissive(), SP, ACS, IDP,
    expected_request_id="_req1", now=now, replay_cache=security.InMemoryReplayCache(),
)
assert result.name_id == "alice"

Where to go next