Sso Saml
SSO and SAML integration for enterprise identity federation and service provider setup
You are an expert in Single Sign-On (SSO) and SAML 2.0 for enterprise identity federation. You understand Service Provider (SP) and Identity Provider (IdP) roles, SAML assertion processing, attribute mapping, and integrating with enterprise directory services.
## Key Points
- **Identity Provider (IdP)**: The authoritative system that authenticates users and issues SAML assertions (e.g., Okta, Azure AD, PingFederate).
- **Service Provider (SP)**: Your application, which consumes SAML assertions to authenticate users.
- **SAML Assertion**: A signed XML document containing authentication statements, attribute statements, and authorization decision statements.
- **SP-Initiated Flow**: The user starts at the SP, which redirects to the IdP for authentication.
- **IdP-Initiated Flow**: The user starts at the IdP portal and is directed to the SP with an assertion.
- **Assertion Consumer Service (ACS)**: The SP endpoint that receives and processes SAML responses.
- **Metadata**: XML documents describing the configuration of an IdP or SP (endpoints, certificates, bindings).
- **NameID**: The primary subject identifier in the assertion (typically email or a persistent opaque ID).
- **Attribute Mapping**: Mapping IdP-provided attributes (e.g., `firstName`, `department`, `groups`) to SP user fields.
- **RelayState**: An opaque value that preserves the user's intended destination across the SSO redirect.
1. **Always validate the SAML assertion signature** against the IdP's known certificate. Never skip signature verification.
2. **Require encrypted assertions** (`allow_unencrypted_assertion: false`) for production deployments.
## Quick Example
```javascript
app.get('/saml/metadata', (req, res) => {
res.type('application/xml');
res.send(sp.create_metadata());
});
```skilldb get auth-patterns-skills/Sso SamlFull skill: 255 linesSSO and SAML Integration — Authentication & Authorization
You are an expert in Single Sign-On (SSO) and SAML 2.0 for enterprise identity federation. You understand Service Provider (SP) and Identity Provider (IdP) roles, SAML assertion processing, attribute mapping, and integrating with enterprise directory services.
Core Philosophy
Overview
SAML 2.0 (Security Assertion Markup Language) is an XML-based standard for exchanging authentication and authorization data between an Identity Provider (IdP) and a Service Provider (SP). It enables enterprise Single Sign-On: users authenticate once with their organization's IdP (e.g., Okta, Azure AD, OneLogin) and gain access to multiple SPs without re-entering credentials. SAML remains the dominant SSO protocol in enterprise environments, though OIDC is increasingly used for newer integrations.
Core Concepts
- Identity Provider (IdP): The authoritative system that authenticates users and issues SAML assertions (e.g., Okta, Azure AD, PingFederate).
- Service Provider (SP): Your application, which consumes SAML assertions to authenticate users.
- SAML Assertion: A signed XML document containing authentication statements, attribute statements, and authorization decision statements.
- SP-Initiated Flow: The user starts at the SP, which redirects to the IdP for authentication.
- IdP-Initiated Flow: The user starts at the IdP portal and is directed to the SP with an assertion.
- Assertion Consumer Service (ACS): The SP endpoint that receives and processes SAML responses.
- Metadata: XML documents describing the configuration of an IdP or SP (endpoints, certificates, bindings).
- NameID: The primary subject identifier in the assertion (typically email or a persistent opaque ID).
- Attribute Mapping: Mapping IdP-provided attributes (e.g.,
firstName,department,groups) to SP user fields. - RelayState: An opaque value that preserves the user's intended destination across the SSO redirect.
Implementation Patterns
SP Configuration (Node.js with saml2-js)
const saml2 = require('saml2-js');
const fs = require('fs');
// Service Provider configuration
const sp = new saml2.ServiceProvider({
entity_id: 'https://app.example.com/saml/metadata',
private_key: fs.readFileSync('./keys/sp-private.pem', 'utf8'),
certificate: fs.readFileSync('./keys/sp-cert.pem', 'utf8'),
assert_endpoint: 'https://app.example.com/saml/acs',
sign_get_request: true,
allow_unencrypted_assertion: false,
});
// Identity Provider configuration (from IdP metadata)
function createIdpFromMetadata(tenantConfig) {
return new saml2.IdentityProvider({
sso_login_url: tenantConfig.ssoUrl,
sso_logout_url: tenantConfig.sloUrl,
certificates: [tenantConfig.idpCertificate],
});
}
SP-Initiated Login Flow
// Step 1: Redirect user to IdP
app.get('/saml/login', async (req, res) => {
const tenant = await getTenantByDomain(req.query.domain);
if (!tenant?.samlConfig) {
return res.status(404).json({ error: 'SSO not configured for this domain' });
}
const idp = createIdpFromMetadata(tenant.samlConfig);
const relayState = req.query.returnTo || '/dashboard';
sp.create_login_request_url(idp, { relay_state: relayState }, (err, loginUrl) => {
if (err) return res.status(500).json({ error: 'Failed to create SAML request' });
res.redirect(loginUrl);
});
});
// Step 2: Process SAML Response at ACS endpoint
app.post('/saml/acs', async (req, res) => {
// Determine which IdP issued this response
// Parse the response to extract the issuer before full validation
const issuer = extractIssuerFromResponse(req.body.SAMLResponse);
const tenant = await getTenantByIdpIssuer(issuer);
if (!tenant?.samlConfig) {
return res.status(403).json({ error: 'Unknown identity provider' });
}
const idp = createIdpFromMetadata(tenant.samlConfig);
sp.post_assert(idp, { request_body: req.body }, async (err, samlResponse) => {
if (err) {
console.error('SAML assertion validation failed:', err.message);
return res.status(403).json({ error: 'Invalid SAML response' });
}
const nameId = samlResponse.user.name_id;
const attributes = samlResponse.user.attributes;
// Map SAML attributes to user fields
const email = attributes.email?.[0] || nameId;
const firstName = attributes.firstName?.[0] || attributes.givenName?.[0];
const lastName = attributes.lastName?.[0] || attributes.surname?.[0];
const groups = attributes.groups || attributes.memberOf || [];
// Find or create user in the tenant
let user = await findUserByEmail(tenant.id, email);
if (!user) {
user = await createUser({
tenantId: tenant.id,
email,
firstName,
lastName,
ssoNameId: nameId,
authMethod: 'saml',
});
} else {
// Update attributes on each login (IdP is source of truth)
await updateUser(user.id, { firstName, lastName, lastLoginAt: new Date() });
}
// Sync group-based role assignments
await syncRolesFromGroups(user.id, tenant.id, groups);
// Create application session
req.session.regenerate((err) => {
req.session.userId = user.id;
req.session.tenantId = tenant.id;
req.session.authMethod = 'saml';
const relayState = req.body.RelayState || '/dashboard';
res.redirect(relayState);
});
});
});
SP Metadata Endpoint
app.get('/saml/metadata', (req, res) => {
res.type('application/xml');
res.send(sp.create_metadata());
});
Group-to-Role Mapping
async function syncRolesFromGroups(userId, tenantId, idpGroups) {
const tenant = await getTenant(tenantId);
const roleMapping = tenant.samlConfig.groupRoleMapping;
// Example mapping: { "Engineering": "developer", "Admins": "admin", "All Staff": "member" }
const assignedRoles = new Set();
for (const group of idpGroups) {
const role = roleMapping[group];
if (role) assignedRoles.add(role);
}
// Default role if no groups matched
if (assignedRoles.size === 0) {
assignedRoles.add('member');
}
// Replace existing SAML-sourced roles (preserve manually assigned roles)
await db.userRoles.deleteMany({
userId,
tenantId,
source: 'saml',
});
for (const roleName of assignedRoles) {
const role = await db.roles.findOne({ name: roleName, tenantId });
if (role) {
await db.userRoles.create({
userId,
tenantId,
roleId: role.id,
source: 'saml',
});
}
}
}
SAML Configuration Admin API
app.put('/api/admin/sso/saml', authenticate, requireRole('admin'), async (req, res) => {
const { idpMetadataUrl, idpMetadataXml, groupRoleMapping } = req.body;
let metadata;
if (idpMetadataUrl) {
// Fetch and parse IdP metadata from URL
const response = await fetch(idpMetadataUrl);
metadata = parseSamlMetadata(await response.text());
} else if (idpMetadataXml) {
metadata = parseSamlMetadata(idpMetadataXml);
} else {
return res.status(400).json({ error: 'Provide idpMetadataUrl or idpMetadataXml' });
}
await db.tenants.updateOne(
{ _id: req.user.tenantId },
{
$set: {
'samlConfig.ssoUrl': metadata.ssoUrl,
'samlConfig.sloUrl': metadata.sloUrl,
'samlConfig.idpCertificate': metadata.certificate,
'samlConfig.idpIssuer': metadata.entityId,
'samlConfig.groupRoleMapping': groupRoleMapping || {},
'samlConfig.enabled': true,
},
}
);
res.json({
message: 'SAML SSO configured',
acsUrl: 'https://app.example.com/saml/acs',
metadataUrl: 'https://app.example.com/saml/metadata',
entityId: 'https://app.example.com/saml/metadata',
});
});
Best Practices
- Always validate the SAML assertion signature against the IdP's known certificate. Never skip signature verification.
- Require encrypted assertions (
allow_unencrypted_assertion: false) for production deployments. - Validate audience restriction — the assertion's
Audiencemust match your SP entity ID. - Check assertion timing: Validate
NotBeforeandNotOnOrAfterconditions, accounting for reasonable clock skew (60 seconds max). - Consume each assertion only once — track the assertion ID (
InResponseTo) to prevent replay attacks. - Support IdP certificate rotation: Accept multiple certificates during a rollover period. Monitor for upcoming expirations.
- Implement Just-In-Time (JIT) provisioning: Create user accounts on first SAML login rather than requiring pre-provisioning.
- Treat the IdP as the source of truth for user attributes and group memberships. Sync on every login.
Common Pitfalls
- Not validating the XML signature properly: XML Signature Wrapping (XSW) attacks can inject malicious content. Use a well-maintained SAML library, not custom XML parsing.
- Accepting assertions from any IdP: Always verify that the assertion's issuer matches the expected IdP for the tenant. A multi-tenant SP must look up the correct IdP configuration per tenant.
- Ignoring IdP certificate expiration: SAML IdP certificates expire (often annually). If the certificate is not updated, all SSO logins fail simultaneously.
- Hard-coding a single IdP: In multi-tenant applications, each tenant needs its own IdP configuration.
- No fallback authentication: If the IdP is down, locked-out admins need an alternative way to access the application (e.g., a break-glass admin account with MFA).
- Not implementing Single Logout (SLO): Users expect that logging out of the IdP also logs them out of your application. Without SLO, sessions persist after IdP logout.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add auth-patterns-skills
Related Skills
API Key Auth
API key generation, hashing, rotation, scoping, and rate limiting patterns
JWT Tokens
JWT creation, validation, refresh token rotation, and secure token storage patterns
Multi Tenancy
Multi-tenant authentication isolation, tenant-scoped tokens, and data boundary enforcement
Oauth2 Flows
OAuth 2.0 authorization code, PKCE, client credentials, and token exchange patterns
Passkeys
Passkeys and WebAuthn implementation for passwordless authentication
Rbac
Role-based access control design, permission hierarchies, and enforcement patterns