Loading learning content...
Consider the modern enterprise employee: they access email, HR systems, expense reporting, CRM, development tools, and dozens of SaaS applications daily. Without Single Sign-On (SSO), each system requires a separate login—separate passwords to remember, separate sessions to manage, and separate security risks to monitor. Multiply this across thousands of employees, and you have a security and usability nightmare.
Single Sign-On (SSO) solves this by enabling users to authenticate once and access multiple applications without re-entering credentials. Identity Federation extends this across organizational boundaries—allowing employees to access partner systems, customers to use multiple related services, and developers to 'Sign in with GitHub' on third-party platforms.
These patterns have become table stakes for enterprise software and consumer applications alike. Understanding SSO and federation is essential for designing systems that integrate with the broader identity ecosystem.
This page covers SSO fundamentals and implementation patterns, the SAML protocol for enterprise federation, OAuth 2.0 and OpenID Connect for modern SSO, social login integration, architectural considerations for service providers, and security implications of federated identity.
Single Sign-On (SSO) and Identity Federation are related but distinct concepts:
SSO can exist without cross-organization federation (a company uses one IdP for all internal apps). Federation typically enables SSO but could theoretically require re-authentication for each service.
Key Terminology:
| Term | Definition | Example |
|---|---|---|
| Identity Provider (IdP) | The system that authenticates users and issues identity assertions | Okta, Azure AD, Auth0, Google |
| Service Provider (SP) | The application that relies on the IdP for authentication | Salesforce, Slack, your internal apps |
| Relying Party (RP) | Synonym for Service Provider in OIDC terminology | Your web application |
| Assertion/Token | Cryptographic proof of authentication from IdP | SAML assertion, OIDC ID token |
| Federation Trust | The configured relationship between IdP and SP | Company A trusts Company B's IdP |
| Claims/Attributes | User information in the assertion (name, email, groups) | email=alice@corp.com, groups=[engineering, admin] |
Why SSO Matters:
SSO increases the value of each account to attackers—one compromised credential grants access to everything. This is why SSO must be paired with strong MFA. Organizations that implement SSO without MFA are often worse off than with separate accounts, because they've centralized all risk into one authentication event.
Security Assertion Markup Language (SAML) 2.0 is the dominant protocol for enterprise SSO. Published in 2005, SAML enabled federated authentication before OAuth existed and remains the standard for B2B and enterprise integrations.
SAML Core Concepts:
The SAML Web Browser SSO Flow:
SAML Assertion Structure:
A SAML assertion is an XML document with three main sections:
<saml:Assertion ...>
<saml:Issuer>https://idp.corp.com</saml:Issuer>
<saml:Subject>
<saml:NameID>alice@corp.com</saml:NameID>
</saml:Subject>
<saml:Conditions NotBefore="..." NotOnOrAfter="...">
<saml:AudienceRestriction>
<saml:Audience>https://app.example.com</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Attribute Name="groups">
<saml:AttributeValue>engineering</saml:AttributeValue>
<saml:AttributeValue>admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<ds:Signature>...</ds:Signature>
</saml:Assertion>
SAML vulnerabilities have occurred when SPs accepted unsigned assertions or validated signatures incorrectly. The assertion's cryptographic signature is the ONLY proof it came from the trusted IdP. Failure to validate allows attackers to forge assertions and impersonate any user. Use well-tested SAML libraries—don't implement validation yourself.
| Configuration Item | Purpose | Security Note |
|---|---|---|
| IdP Metadata URL | Contains IdP's signing certificate, SSO endpoint | Fetch from HTTPS; verify certificate |
| SP Entity ID | Unique identifier for your SP | Must match AudienceRestriction in assertions |
| ACS URL | Where IdP posts assertions | Must be HTTPS; validate in assertion Destination |
| Signing Certificate | SP's private key for signing requests | Protect private key; rotate periodically |
| NameID Format | How users are identified | Persistent for long-lived accounts; email for simplicity |
| Attribute Mapping | Map SAML attributes to your user model | Validate expected attributes exist |
While SAML dominated enterprise SSO, the mobile and API era demanded something lighter. OAuth 2.0 (2012) provided delegated authorization with JSON and simple HTTP, and OpenID Connect (OIDC) (2014) layered authentication on top of OAuth.
OAuth 2.0 is an authorization framework—it answers 'What can this app access on the user's behalf?' not 'Who is this user?' OIDC adds identity by defining:
| Grant Type | Use Case | Token Flow |
|---|---|---|
| Authorization Code | Server-side web apps, SPAs (with PKCE) | User redirected to IdP; code exchanged for tokens |
| Authorization Code + PKCE | Public clients (mobile, SPA) that can't keep secrets | Same as above with code_verifier proof |
| Client Credentials | Machine-to-machine, no user context | App authenticates with client_id + secret |
| Device Code | Input-constrained devices (TV, CLI) | User authorizes on different device |
| Implicit (Deprecated) | Legacy SPAs without backend | Tokens in URL fragment; avoid for new apps |
| Password (Deprecated) | Legacy trusted first-party apps | Direct password exchange; avoid entirely |
The Authorization Code Flow with PKCE:
This is the recommended flow for most web and mobile applications:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
import { Issuer, generators } from 'openid-client'; // Configurationconst OIDC_ISSUER = 'https://accounts.google.com';const CLIENT_ID = process.env.GOOGLE_CLIENT_ID!;const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET!;const REDIRECT_URI = 'https://myapp.com/callback'; // Discover OIDC provider endpointsconst googleIssuer = await Issuer.discover(OIDC_ISSUER);console.log('Discovered issuer:', googleIssuer.metadata); // Create clientconst client = new googleIssuer.Client({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uris: [REDIRECT_URI], response_types: ['code'],}); // Step 1: Generate authorization URLfunction generateAuthUrl(req: Request): string { const code_verifier = generators.codeVerifier(); const code_challenge = generators.codeChallenge(code_verifier); const state = generators.state(); const nonce = generators.nonce(); // Store in session for callback validation req.session.oidc = { code_verifier, state, nonce }; return client.authorizationUrl({ scope: 'openid email profile', code_challenge, code_challenge_method: 'S256', state, nonce, });} // Step 2: Handle callbackasync function handleCallback(req: Request) { const { code_verifier, state, nonce } = req.session.oidc; const params = client.callbackParams(req); // Exchange code for tokens const tokenSet = await client.callback( REDIRECT_URI, params, { code_verifier, state, nonce, // Validates nonce claim in id_token } ); // Token validation happens automatically console.log('ID Token claims:', tokenSet.claims()); // { sub: '123456789', email: 'user@gmail.com', name: 'Alice', ... } // Fetch additional user info if needed const userinfo = await client.userinfo(tokenSet.access_token!); return { userId: tokenSet.claims().sub, email: tokenSet.claims().email, name: tokenSet.claims().name, accessToken: tokenSet.access_token, refreshToken: tokenSet.refresh_token, };}PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. Even if an attacker intercepts the authorization code, they can't exchange it for tokens without the code_verifier that only the legitimate client knows. Always use PKCE for public clients (mobile, SPA) and recommended even for confidential clients.
Social login (also called social sign-in) allows users to authenticate using existing accounts from major providers—Google, Facebook, Apple, GitHub, Microsoft. For consumer applications, social login dramatically reduces signup friction and password fatigue.
Implementation Considerations:
| Provider | Protocol | Key Considerations | Audience |
|---|---|---|---|
| OIDC | Widely trusted; good OIDC compliance; email verified | Consumer, enterprise (Workspace) | |
| Apple | OIDC | Required for iOS apps with social login; email relay option | Consumer, especially iOS users |
| OAuth 2.0 (not OIDC) | Large user base; privacy concerns; uses custom endpoints | Consumer | |
| Microsoft | OIDC | Personal and work accounts; Azure AD integration | Consumer, enterprise |
| GitHub | OAuth 2.0 | Developer-focused; access to repos if scoped | Developer tools, open source |
| Twitter/X | OAuth 2.0 | Limited user info; frequent API changes | Social apps, news |
Account Linking Challenges:
When users can log in with multiple methods (email/password, Google, GitHub), you need an account linking strategy:
Same Email = Same Account? — If a user signs up with email alice@gmail.com, then later clicks 'Sign in with Google' using the same email, are these the same account? Usually yes, but requires verification.
Email Verification — Social providers typically guarantee email verification. If they don't (or you can't confirm), you might be linking to the wrong account.
Multiple Social Accounts — A user might want to link both Google and GitHub. Design for this from the start.
Orphaned Accounts — What happens if a user deletes their Google account? They need an alternative login method.
Account Linking Design:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
interface User { id: string; email: string; emailVerified: boolean; passwordHash?: string; // Optional if only social linkedAccounts: LinkedAccount[];} interface LinkedAccount { provider: 'google' | 'github' | 'apple' | 'facebook'; providerUserId: string; // Unique ID from provider email?: string; // Email from this provider linkedAt: Date;} async function handleSocialLogin( provider: string, providerUserId: string, providerEmail: string, emailVerified: boolean): Promise<{ user: User; isNewUser: boolean }> { // 1. Check if this provider account is already linked const existingLink = await db.linkedAccounts.findOne({ provider, providerUserId, }); if (existingLink) { // Known user, return their account const user = await db.users.findById(existingLink.userId); return { user, isNewUser: false }; } // 2. Check if email matches an existing user if (providerEmail && emailVerified) { const existingUser = await db.users.findByEmail(providerEmail); if (existingUser) { // Link this provider to existing account await db.linkedAccounts.create({ userId: existingUser.id, provider, providerUserId, email: providerEmail, linkedAt: new Date(), }); // Optionally notify user of new linked account await sendEmail(existingUser.email, 'New login method added', `Your ${provider} account is now linked...` ); return { user: existingUser, isNewUser: false }; } } // 3. Create new user const newUser = await db.users.create({ email: providerEmail, emailVerified: emailVerified, linkedAccounts: [{ provider, providerUserId, email: providerEmail, linkedAt: new Date(), }], }); return { user: newUser, isNewUser: true };}Apple requires 'Sign in with Apple' for iOS apps that offer any third-party login. Apple also provides email relay addresses that hide user's real email. Your account linking logic must handle relay addresses that differ from the user's actual email. Track the Apple 'sub' claim as the stable identifier, not email.
When selling to enterprises, SSO support transitions from nice-to-have to mandatory. IT departments require SSO for security (centralized access control), compliance (audit trails), and efficiency (automated provisioning/deprovisioning).
Enterprise SSO Requirements:
Multi-Tenancy Considerations:
B2B SaaS applications typically support multiple enterprise customers, each with their own IdP configuration:
| Approach | Description | Trade-offs |
|---|---|---|
| Subdomain per Tenant | customer1.app.com, customer2.app.com | Clean separation; more infrastructure complexity |
| Email Domain Detection | Detect SSO config from email domain at login | Simple UX; requires email upfront; domain conflicts possible |
| Tenant Selector | User selects organization from list | Clear intent; extra step; works for multi-domain orgs |
| Connection ID in URL | Login links include tenant identifier | Works for IdP-initiated flows; less discoverable |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
interface SSOConnection { id: string; organizationId: string; provider: 'saml' | 'oidc'; enabled: boolean; domains: string[]; // Verified email domains enforced: boolean; // Require SSO, disable password // SAML configuration samlIdpEntityId?: string; samlSsoUrl?: string; samlCertificate?: string; samlNameIdFormat?: string; // OIDC configuration oidcIssuer?: string; oidcClientId?: string; oidcClientSecret?: string; // Encrypted at rest // Attribute mapping attributeMapping: { email: string; // Path to email in assertion firstName?: string; lastName?: string; groups?: string; }; // Role assignment defaultRole: string; groupRoleMapping: Record<string, string>; // IdP group -> app role} // Determine SSO connection from emailasync function detectSSOConnection(email: string): Promise<SSOConnection | null> { const domain = email.split('@')[1]; const connection = await db.ssoConnections.findOne({ domains: domain, enabled: true, }); return connection;} // Login flow with SSO detectionasync function initiateLogin(email: string): Promise<LoginAction> { const ssoConnection = await detectSSOConnection(email); if (ssoConnection) { if (ssoConnection.enforced) { // Must use SSO, no password option return { action: 'redirect_sso', ssoUrl: generateSSOUrl(ssoConnection), }; } else { // SSO available but optional return { action: 'choice', options: ['password', 'sso'], ssoUrl: generateSSOUrl(ssoConnection), }; } } // No SSO configured, use password return { action: 'password_prompt' };}Building multi-provider SSO from scratch is complex. Platforms like Auth0, WorkOS, Clerk, and Stytch handle SAML/OIDC integration, certificate management, and multi-tenancy. Evaluate build vs buy carefully—SSO is not your core product.
SSO introduces session management complexity that single-application authentication doesn't face. Users now have sessions at both the IdP and each SP, and these sessions must coordinate correctly.
Session Types in SSO:
Single Logout (SLO) Challenge:
When a user logs out of one application, should they be logged out of all applications? This is Single Logout, and it's notoriously difficult to implement correctly.
SAML Single Logout:
Problems:
Many organizations disable SAML SLO because it's unreliable and creates confusing user experiences. If perfect logout across all apps is required, consider short-lived sessions with forced re-authentication rather than attempting to coordinate logout across a federation.
Alternative Approaches:
Short-Lived SP Sessions — SP sessions expire after 4-8 hours; user re-authenticates against IdP (which still has session) seamlessly.
Front-Channel Logout in OIDC — IdP renders invisible iframes to each SP's logout endpoint. More reliable than SAML back-channel.
Token Revocation Checking — SP periodically calls IdP to verify session/token is still valid via introspection or userinfo call.
Session Sync Service — Custom service that SPs check before honoring any request; IdP updates this on logout.
Session Timeout Strategies:
Balance security and usability:
Federation introduces security risks that don't exist in self-contained authentication. You're now trusting external identity providers and accepting cryptographic assertions from systems outside your control.
| Risk | Description | Mitigation |
|---|---|---|
| IdP Compromise | If the IdP is breached, attackers can generate valid assertions | Strong IdP security, MFA at IdP, breach monitoring |
| Signature Bypass | Vulnerabilities in assertion validation allow forgery | Use battle-tested libraries; never custom XML parsing |
| Token Theft | Access tokens or SAML assertions intercepted | HTTPS everywhere; short token lifetimes; token binding |
| Session Fixation | Attacker sets session before SSO login completes | Regenerate session IDs after login; validate state parameter |
| Open Redirect | Redirect parameter manipulation after login | Strict redirect URL validation; whitelist allowed URLs |
| Account Takeover via Linking | Attacker links their IdP to victim's account | Verify email ownership before linking; notify user |
| Privilege Escalation via Attributes | Attacker modifies claims at IdP to gain access | Attribute validation; don't blindly trust group memberships |
Assertion Validation Checklist:
Every assertion/token received must be validated:
SAML has a history of XML signature wrapping vulnerabilities where attackers manipulate XML structure while preserving valid signatures. The unsigned portions contain attacker-controlled data. Never parse SAML XML manually; use libraries specifically designed for SAML that understand these attack classes.
SSO and federation are essential capabilities for modern applications—enabling seamless user experiences while maintaining centralized security control. Understanding both enterprise (SAML) and modern (OIDC) approaches ensures you can integrate with any identity ecosystem.
What's Next:
The final page of this module explores Passwordless Authentication—the emerging paradigm that replaces passwords entirely with cryptographic keys, biometrics, and magic links. We'll examine WebAuthn passkeys, email magic links, biometric-only flows, and how to design systems that never require users to remember secrets.
You now understand SSO and federation from architectural principles through implementation details. You can integrate with enterprise identity providers via SAML or OIDC, implement social login with proper account linking, design multi-tenant SSO configurations, and secure federated authentication against common attacks.