SSO Fundamentals

SSO Architecture

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.