SSO Architecture


SSO Fundamentals

Single Sign-On (SSO) allows users to authenticate once and access multiple applications without re-entering credentials. It improves security by centralizing authentication and reducing password fatigue.

SAML 2.0

Security Assertion Markup Language (SAML) is the mature standard for enterprise SSO:






AssertionConsumerServiceURL="https://app.example.com/saml/acs"


Destination="https://idp.example.com/saml/sso"


IssueInstant="2026-05-12T10:00:00Z">


https://app.example.com



Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>









# SAML response parsing


from signxml import XMLVerifier


import xml.etree.ElementTree as ET




def parse_saml_response(response_xml):


# Verify the signature


verified_data = XMLVerifier().verify(response_xml).signed_xml




# Extract attributes


ns = {"saml2": "urn:oasis:names:tc:SAML:2.0:assertion"}


root = ET.fromstring(verified_data)




attributes = {}


for attr in root.findall(".//saml2:Attribute", ns):


name = attr.get("Name")


values = [v.text for v in attr.findall("saml2:AttributeValue", ns)]


attributes[name] = values




return attributes





OpenID Connect (OIDC)

OIDC is the modern SSO protocol built on OAuth2:




// OIDC client configuration


const { Issuer } = require("openid-client");




async function configureOIDC() {


const issuer = await Issuer.discover("https://accounts.example.com");




const client = new issuer.Client({


client_id: process.env.CLIENT_ID,


client_secret: process.env.CLIENT_SECRET,


redirect_uris: ["https://app.example.com/callback"],


response_types: ["code"],


token_endpoint_auth_method: "client_secret_basic"


});




return client;


}




// Generate authentication URL


async function login(req, res) {


const client = await configureOIDC();


const authUrl = client.authorizationUrl({


scope: "openid profile email",


state: crypto.randomBytes(16).toString("hex"),


nonce: crypto.randomBytes(16).toString("hex")


});




req.session.oidcState = state;


res.redirect(authUrl);


}





Token Exchange




# Token exchange handler


async def handle_callback(request):


code = request.query_params["code"]


state = request.query_params["state"]




# Verify state matches


if state != request.session["oidc_state"]:


raise SecurityError("State mismatch - possible CSRF")




# Exchange code for tokens


token_response = await oidc_client.authorize_token(code)




# Validate ID token


claims = await oidc_client.validate_id_token(


token_response["id_token"],


nonce=request.session["oidc_nonce"]


)




return {


"access_token": token_response["access_token"],


"user": claims


}





Session Management




session_management:


storage:


type: redis


ttl: 3600 # 1 hour


extend_on_activity: true




token_strategy:


access_token_ttl: 15m


refresh_token_ttl: 7d


rotate_refresh: true




logout:


- clear local session


- call IdP logout endpoint (SLO)


- revoke all tokens for user





IdP Integration Patterns

| Protocol | Use Case | Complexity | Security | |----------|----------|------------|----------| | SAML | Enterprise, government | High | Strong | | OIDC | Modern web, mobile | Medium | Strong | | LDAP | Legacy applications | Low | Weak |

Conclusion

SSO centralizes authentication and improves both security and user experience. Choose SAML for enterprise integrations and OIDC for modern applications. Implement proper session management with short-lived tokens and single logout. Always validate state parameters and ID token signatures.