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