---
# === IDENTITY ===
id: software/patterns/sso-saml-oidc/2026
canonical_question: "How do I implement SSO with SAML and OpenID Connect?"
aliases:
  - "SAML 2.0 SSO implementation guide"
  - "OpenID Connect SSO setup"
  - "SAML vs OIDC comparison"
  - "Single Sign-On SAML service provider"
  - "OIDC relying party implementation"
  - "Enterprise SSO federation protocol"
  - "SAML assertion validation"
  - "OpenID Connect Discovery integration"
entity_type: software_reference
domain: software > patterns > SSO with SAML and OpenID Connect
region: global
jurisdiction: global
temporal_scope: 2005-2026

# === VERIFICATION ===
last_verified: 2026-02-24
confidence: 0.90
version: 1.0
first_published: 2026-02-24

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "SAML 2.0 finalized 2005-03; OpenID Connect 1.0 finalized 2014-02; OIDC Federation 1.0 draft 2024"
  next_review: 2026-08-23
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Always validate SAML assertion XML signatures server-side — never trust unsigned or client-validated assertions"
  - "OIDC nonce parameter must be validated on every authentication response to prevent replay attacks"
  - "Always validate the issuer (iss) and audience (aud) claims in both SAML assertions and OIDC ID tokens"
  - "SAML assertions must be validated within the NotBefore/NotOnOrAfter time window with clock skew tolerance"
  - "Never store SAML assertions or OIDC tokens in localStorage — use httpOnly secure cookies or server-side sessions"
  - "SAML XML parsing must use defenses against XML Signature Wrapping (XSW) attacks"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need OAuth 2.0 API authorization without user identity (machine-to-machine)"
    use_instead: "software/patterns/oauth2-client-credentials/2026"
  - condition: "Building a custom identity provider from scratch"
    use_instead: "software/system-design/identity-provider-architecture/2026"
  - condition: "Only need social login (Google, GitHub, Facebook) without enterprise SSO"
    use_instead: "software/patterns/oauth2-authorization-code-pkce/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: protocol
    question: "Which SSO protocol do you need to implement?"
    type: choice
    options: ["SAML 2.0", "OpenID Connect", "Both (bridge/federation)"]
  - key: role
    question: "What role does your application play?"
    type: choice
    options: ["Service Provider (SP) / Relying Party (RP)", "Identity Provider (IdP)", "Both (proxy/bridge)"]
  - key: identity_provider
    question: "Which identity provider will you integrate with?"
    type: choice
    options: ["Okta", "Azure AD (Entra ID)", "Google Workspace", "AWS IAM Identity Center", "Keycloak", "Custom/Other"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/patterns/sso-saml-oidc/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-24)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "software/patterns/oauth2-authorization-code-pkce/2026"
      label: "OAuth 2.0 Authorization Code with PKCE"
  related_to:
    - id: "software/patterns/jwt-implementation/2026"
      label: "JWT Implementation and Validation"
    - id: "software/patterns/session-management/2026"
      label: "Session Management Patterns"
    - id: "software/patterns/rbac-implementation/2026"
      label: "Role-Based Access Control Implementation"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/patterns/oauth2-authorization-code-pkce/2026"
      label: "OAuth 2.0 (authorization, not authentication — OIDC adds the identity layer)"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "SAML V2.0 Technical Overview"
    author: OASIS
    url: https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html
    type: rfc_spec
    published: 2008-03-25
    reliability: authoritative
  - id: src2
    title: "OpenID Connect Discovery 1.0"
    author: OpenID Foundation
    url: https://openid.net/specs/openid-connect-discovery-1_0.html
    type: rfc_spec
    published: 2014-11-08
    reliability: authoritative
  - id: src3
    title: "OAuth 2.0 and OpenID Connect Overview"
    author: Okta
    url: https://developer.okta.com/docs/concepts/oauth-openid/
    type: official_docs
    published: 2025-01-15
    reliability: high
  - id: src4
    title: "SAML Configuration — Auth0 Docs"
    author: Auth0
    url: https://auth0.com/docs/authenticate/protocols/saml/saml-configuration
    type: official_docs
    published: 2025-06-01
    reliability: high
  - id: src5
    title: "OpenID Connect (OIDC) on the Microsoft Identity Platform"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc
    type: official_docs
    published: 2025-09-15
    reliability: high
  - id: src6
    title: "passport-saml — SAML 2.0 Authentication for Passport"
    author: node-saml
    url: https://github.com/node-saml/passport-saml
    type: community_resource
    published: 2025-11-01
    reliability: moderate_high
  - id: src7
    title: "SAML vs OpenID Connect (OIDC) — Auth0"
    author: Auth0
    url: https://auth0.com/intro-to-iam/saml-vs-openid-connect-oidc
    type: technical_blog
    published: 2025-03-01
    reliability: high
---

# SSO with SAML 2.0 and OpenID Connect

## TL;DR

- **Bottom line**: Use OpenID Connect for new greenfield apps and consumer-facing SSO; use SAML 2.0 for enterprise integrations with legacy IdPs that only support SAML.
- **Key tool/command**: `/.well-known/openid-configuration` (OIDC Discovery endpoint) or SAML metadata XML at IdP/SP metadata URLs.
- **Watch out for**: Not validating XML signatures on SAML assertions server-side — this is the #1 vulnerability in SAML implementations.
- **Works with**: Any language/framework. SAML 2.0 (2005+, XML-based), OIDC 1.0 (2014+, JSON/JWT-based). All major IdPs (Okta, Azure AD, Google, Keycloak, AWS IAM Identity Center).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Always validate SAML assertion XML signatures server-side using the IdP's public certificate — never accept unsigned assertions or validate only on the client
- OIDC `nonce` must be generated per-request and validated in the ID token to prevent replay attacks
- Validate `iss` (issuer) and `aud` (audience) claims in both SAML assertions and OIDC ID tokens — mismatched values indicate token substitution attacks
- SAML assertions must be consumed within the `NotBefore` / `NotOnOrAfter` time window (allow 2-3 minutes clock skew maximum)
- Never store tokens or assertions in `localStorage` — use `httpOnly`, `Secure`, `SameSite=Lax` cookies or server-side session stores
- Enable XML External Entity (XXE) protection and defenses against XML Signature Wrapping (XSW) attacks in all SAML XML parsers

## Quick Reference

| Feature | SAML 2.0 | OpenID Connect | When to Choose |
|---|---|---|---|
| **Data format** | XML assertions | JSON/JWT tokens | OIDC for simpler parsing |
| **Transport** | HTTP Redirect, POST, Artifact | HTTPS + JSON (OAuth 2.0 flows) | OIDC for REST APIs |
| **Discovery** | Metadata XML document | `/.well-known/openid-configuration` | OIDC for auto-config |
| **Token type** | XML assertion (signed, optionally encrypted) | ID token (JWT, signed) + access token | OIDC for stateless validation |
| **Identity info** | Attribute statements in assertion | `userinfo` endpoint + ID token claims | Equivalent — both carry user attributes |
| **Session logout** | SLO (Single Logout) protocol | `end_session_endpoint` (RP-Initiated Logout) | SAML SLO is more complex but more reliable |
| **SP/RP setup** | Exchange metadata XML, configure certificates | Register `client_id` + `redirect_uri`, use Discovery | OIDC is faster to integrate |
| **Security model** | XML Signature + optional XML Encryption | JWT signature (RS256/ES256) + TLS | Both strong; OIDC has smaller attack surface |
| **Enterprise adoption** | Dominant (90%+ of enterprise IdPs support it) | Growing rapidly, most new IdPs default to it | SAML if IdP only speaks SAML |
| **Mobile / SPA** | Poor (XML parsing in browser is heavy) | Excellent (native JSON, PKCE flow) | Always OIDC for mobile/SPA |
| **Spec complexity** | High (~70 pages core + bindings + profiles) | Moderate (~30 pages, built on OAuth 2.0) | OIDC if team is new to federation |
| **Typical IdPs** | AD FS, Shibboleth, PingFederate, Okta | Okta, Azure AD, Google, Auth0, Keycloak | Check your IdP's preferred protocol |
| **Maturity** | 20+ years (2005) | 12+ years (2014) | Both production-ready |
| **Renewal/rotation** | Manual certificate rotation | JWKS endpoint with automatic key rotation | OIDC for zero-downtime key rotation |

## Decision Tree

```
START
├── Greenfield application (no existing SSO)?
│   ├── YES → Use OpenID Connect (simpler, modern, better tooling)
│   └── NO ↓
├── IdP only supports SAML (e.g., legacy AD FS, Shibboleth)?
│   ├── YES → Implement SAML 2.0 SP (see Step-by-Step: SAML SP)
│   └── NO ↓
├── Mobile app or Single Page Application (SPA)?
│   ├── YES → Use OIDC with Authorization Code + PKCE
│   └── NO ↓
├── Enterprise customer requiring SAML?
│   ├── YES → Implement SAML 2.0 SP alongside OIDC
│   └── NO ↓
├── Need to support both protocols (multi-tenant SaaS)?
│   ├── YES → Use an identity broker (Keycloak, Auth0) that bridges SAML ↔ OIDC
│   └── NO ↓
├── Regulatory compliance requiring specific protocol (e.g., government)?
│   ├── YES → Check compliance requirements (many US gov specs prefer SAML)
│   └── NO ↓
└── DEFAULT → Use OpenID Connect
```

## Step-by-Step Guide

### SAML 2.0: Implement a Service Provider (SP)

#### 1. Obtain IdP metadata

Request the SAML metadata XML from your Identity Provider. This contains the IdP's entity ID, SSO endpoint URLs, and signing certificate. [src1]

```bash
# Download IdP metadata (example: Okta)
curl -o idp-metadata.xml \
  "https://your-org.okta.com/app/YOUR_APP_ID/sso/saml/metadata"

# Verify the metadata contains required elements
xmllint --xpath "//*[local-name()='SingleSignOnService']/@Location" idp-metadata.xml
```

**Verify**: The metadata XML should contain `<SingleSignOnService>` with a `Location` URL and an `<X509Certificate>` element.

#### 2. Generate SP certificate and metadata

Create your SP's signing/encryption certificate and generate SP metadata XML to share with the IdP. [src4]

```bash
# Generate SP certificate (valid 2 years)
openssl req -x509 -newkey rsa:2048 -keyout sp-key.pem -out sp-cert.pem \
  -days 730 -nodes -subj "/CN=your-app.example.com"
```

**Verify**: `openssl x509 -in sp-cert.pem -text -noout` shows your CN and validity dates.

#### 3. Configure SAML SP in your application

Install the SAML library and configure it with the IdP metadata and SP credentials. [src6]

```javascript
// Node.js with @node-saml/passport-saml v5.x
// npm install @node-saml/passport-saml@5 passport express-session
const passport = require('passport');
const { Strategy: SamlStrategy } = require('@node-saml/passport-saml');
const fs = require('fs');

passport.use(new SamlStrategy(
  {
    callbackUrl: 'https://your-app.example.com/auth/saml/callback',
    entryPoint: 'https://your-org.okta.com/app/YOUR_APP_ID/sso/saml',
    issuer: 'https://your-app.example.com',  // SP entity ID
    cert: fs.readFileSync('./idp-cert.pem', 'utf-8'),  // IdP signing cert
    decryptionPvk: fs.readFileSync('./sp-key.pem', 'utf-8'),
    signatureAlgorithm: 'sha256',
    wantAssertionsSigned: true,     // CRITICAL: require signed assertions
    wantAuthnResponseSigned: true,  // require signed responses
    maxAssertionAgeMs: 300000,      // 5 min max assertion age
  },
  (profile, done) => {
    // profile contains: nameID, email, attributes from IdP
    return done(null, {
      id: profile.nameID,
      email: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'],
      name: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'],
    });
  }
));
```

**Verify**: Start the app and navigate to `/auth/saml/login` — you should be redirected to the IdP login page.

#### 4. Handle the SAML callback and create session

Process the SAML response, validate the assertion, and establish a local session. [src1]

```javascript
const express = require('express');
const session = require('express-session');
const app = express();

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { secure: true, httpOnly: true, sameSite: 'lax', maxAge: 3600000 }
}));
app.use(passport.initialize());
app.use(passport.session());

// Initiate SAML login
app.get('/auth/saml/login', passport.authenticate('saml'));

// SAML callback (IdP POSTs the assertion here)
app.post('/auth/saml/callback',
  passport.authenticate('saml', { failureRedirect: '/login/failed' }),
  (req, res) => {
    res.redirect('/dashboard');
  }
);

// Serialize/deserialize user for session
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
```

**Verify**: Complete a login flow — check that `req.user` is populated with IdP attributes after redirect.

### OpenID Connect: Implement a Relying Party (RP)

#### 5. Register your application with the OIDC provider

Register a client application with your OIDC provider to get `client_id` and `client_secret`. Configure the redirect URI. [src3]

```bash
# Discover OIDC provider configuration
curl -s "https://your-org.okta.com/.well-known/openid-configuration" | jq '.issuer, .authorization_endpoint, .token_endpoint, .jwks_uri, .userinfo_endpoint'
```

**Verify**: The response should contain `issuer`, `authorization_endpoint`, `token_endpoint`, `jwks_uri`, and `userinfo_endpoint`.

#### 6. Implement OIDC Authorization Code flow with PKCE

Use the Authorization Code flow with PKCE for server-side or SPA applications. [src2] [src5]

```javascript
// Node.js with openid-client v6.x
// npm install openid-client@6
const { Issuer, generators } = require('openid-client');

async function setupOidcClient() {
  // Auto-discover OIDC configuration
  const issuer = await Issuer.discover('https://your-org.okta.com');
  console.log('Discovered issuer:', issuer.issuer);

  const client = new issuer.Client({
    client_id: process.env.OIDC_CLIENT_ID,
    client_secret: process.env.OIDC_CLIENT_SECRET,
    redirect_uris: ['https://your-app.example.com/auth/oidc/callback'],
    response_types: ['code'],
  });

  return client;
}

// Generate authorization URL with PKCE
async function getAuthUrl(client) {
  const codeVerifier = generators.codeVerifier();
  const codeChallenge = generators.codeChallenge(codeVerifier);
  const nonce = generators.nonce();  // CRITICAL: generate per-request nonce
  const state = generators.state();

  const url = client.authorizationUrl({
    scope: 'openid profile email',
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
    nonce: nonce,  // Must be validated in ID token
    state: state,  // CSRF protection
  });

  // Store codeVerifier, nonce, state in session
  return { url, codeVerifier, nonce, state };
}
```

**Verify**: Opening the authorization URL should redirect to the IdP login page with correct parameters.

#### 7. Handle the OIDC callback and validate tokens

Exchange the authorization code for tokens and validate the ID token claims. [src3]

```javascript
// OIDC callback handler
app.get('/auth/oidc/callback', async (req, res) => {
  const params = client.callbackParams(req);

  // Exchange code for tokens (validates signature, iss, aud, nonce automatically)
  const tokenSet = await client.callback(
    'https://your-app.example.com/auth/oidc/callback',
    params,
    {
      code_verifier: req.session.codeVerifier,
      nonce: req.session.nonce,     // Validates nonce claim
      state: req.session.state,     // Validates state parameter
    }
  );

  // Extract user info from ID token
  const claims = tokenSet.claims();
  console.log('User:', claims.sub, claims.email, claims.name);

  // Optionally fetch additional claims from userinfo endpoint
  const userinfo = await client.userinfo(tokenSet.access_token);

  // Create session
  req.session.user = {
    id: claims.sub,
    email: claims.email || userinfo.email,
    name: claims.name || userinfo.name,
  };

  // Clean up PKCE/nonce from session
  delete req.session.codeVerifier;
  delete req.session.nonce;
  delete req.session.state;

  res.redirect('/dashboard');
});
```

**Verify**: After login, `req.session.user` should contain the authenticated user's `sub`, `email`, and `name` claims.

## Code Examples

### Python: SAML SP with python3-saml

> Full script: [python-saml-sp-with-python3-saml.py](scripts/python-saml-sp-with-python3-saml.py) (30 lines)

```python
# pip install python3-saml==1.16.0
# Input:  SAML response from IdP (POST to /saml/acs)
# Output: Authenticated user attributes
from onelogin.saml2.auth import OneLogin_Saml2_Auth
def prepare_saml_request(request):
# ... (see full script)
```

### TypeScript: OIDC RP with oidc-client-ts (Browser SPA)

> Full script: [typescript-oidc-rp-with-oidc-client-ts-browser-spa.ts](scripts/typescript-oidc-rp-with-oidc-client-ts-browser-spa.ts) (28 lines)

```typescript
// npm install oidc-client-ts@3
// Input:  OIDC provider configuration
// Output: Authenticated user with ID token claims
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
const userManager = new UserManager({
# ... (see full script)
```

### Go: OIDC Token Validation

> Full script: [go-oidc-token-validation.go](scripts/go-oidc-token-validation.go) (43 lines)

```go
// go get github.com/coreos/go-oidc/v3@v3.11.0
// Input:  Raw ID token string from OIDC callback
// Output: Validated token claims
package main
import (
# ... (see full script)
```

## Anti-Patterns

### Wrong: Trusting SAML assertions without signature validation

```javascript
// BAD — accepting assertion without verifying XML signature
passport.use(new SamlStrategy({
  callbackUrl: 'https://app.example.com/callback',
  entryPoint: 'https://idp.example.com/sso',
  issuer: 'app-entity-id',
  // MISSING: cert, wantAssertionsSigned, wantAuthnResponseSigned
}, (profile, done) => done(null, profile)));
```

### Correct: Always validate signatures and require signed assertions

```javascript
// GOOD — full signature validation configured
passport.use(new SamlStrategy({
  callbackUrl: 'https://app.example.com/callback',
  entryPoint: 'https://idp.example.com/sso',
  issuer: 'app-entity-id',
  cert: fs.readFileSync('./idp-cert.pem', 'utf-8'),
  wantAssertionsSigned: true,
  wantAuthnResponseSigned: true,
  signatureAlgorithm: 'sha256',
  maxAssertionAgeMs: 300000,
}, (profile, done) => done(null, profile)));
```

### Wrong: Skipping nonce validation in OIDC

```javascript
// BAD — not generating or validating nonce (allows replay attacks)
const url = client.authorizationUrl({
  scope: 'openid profile email',
  // MISSING: nonce parameter
});

// In callback — not passing nonce for validation
const tokenSet = await client.callback(redirectUri, params, {
  // MISSING: nonce check
});
```

### Correct: Always generate and validate nonce

```javascript
// GOOD — nonce generated per-request and validated
const nonce = generators.nonce();
req.session.nonce = nonce;

const url = client.authorizationUrl({
  scope: 'openid profile email',
  nonce: nonce,
  state: generators.state(),
  code_challenge: generators.codeChallenge(codeVerifier),
  code_challenge_method: 'S256',
});

// In callback — nonce validated automatically by library
const tokenSet = await client.callback(redirectUri, params, {
  nonce: req.session.nonce,
  state: req.session.state,
  code_verifier: req.session.codeVerifier,
});
```

### Wrong: Storing tokens in localStorage

```typescript
// BAD — XSS can steal tokens from localStorage
const userManager = new UserManager({
  authority: 'https://idp.example.com',
  client_id: 'app',
  redirect_uri: 'https://app.example.com/callback',
  userStore: new WebStorageStateStore({ store: localStorage }),  // VULNERABLE
});
```

### Correct: Use sessionStorage or server-side sessions

```typescript
// GOOD — sessionStorage is tab-scoped and cleared on close
const userManager = new UserManager({
  authority: 'https://idp.example.com',
  client_id: 'app',
  redirect_uri: 'https://app.example.com/callback',
  userStore: new WebStorageStateStore({ store: sessionStorage }),
  // For highest security: use a backend-for-frontend (BFF) pattern
  // that keeps tokens server-side and issues httpOnly cookies
});
```

### Wrong: Not validating audience claim

```python
# BAD — accepting ID token without checking audience
import jwt
token = jwt.decode(id_token, key, algorithms=['RS256'])
# Missing: audience validation — token from another app accepted
user = token['sub']
```

### Correct: Always validate issuer and audience

```python
# GOOD — explicit issuer and audience validation
import jwt
token = jwt.decode(
    id_token,
    key,
    algorithms=['RS256'],
    issuer='https://your-org.okta.com',   # Validates iss claim
    audience='YOUR_CLIENT_ID',             # Validates aud claim
    options={'require': ['exp', 'iss', 'aud', 'sub', 'nonce']},
)
user = token['sub']
```

## Common Pitfalls

- **Clock skew between SP and IdP**: SAML assertions rejected because server clocks differ by more than the allowed tolerance. Fix: configure `maxClockSkew: 180` (3 minutes) in your SAML library and use NTP on all servers. [src1]
- **Certificate rotation breaking SSO**: IdP rotates its signing certificate but SP still has the old certificate pinned. Fix: subscribe to IdP metadata updates and support multiple certificates during rotation windows. [src3]
- **Missing `RelayState` forwarding**: Users lose their deep-link destination after SSO login because RelayState is not passed through. Fix: pass the original URL as `RelayState` in the SAML AuthnRequest and redirect to it after successful authentication. [src4]
- **OIDC `state` parameter mismatch**: CSRF attacks succeed because the `state` parameter is not validated or is reused. Fix: generate a cryptographically random `state` per request, store it in the session, and validate it in the callback. [src2]
- **XML Signature Wrapping (XSW) attacks**: Attacker manipulates SAML XML to inject a forged assertion that passes signature validation. Fix: use a well-maintained SAML library (python3-saml, passport-saml v5+) that defends against all 8 known XSW variants. [src7]
- **Single Logout (SLO) not implemented**: Users remain logged into the IdP after logging out of the SP, creating a security risk on shared devices. Fix: implement SAML SLO or OIDC RP-Initiated Logout, and expire local sessions independently. [src1]
- **OIDC token stored beyond session lifetime**: Access tokens or ID tokens persist after the session should have expired. Fix: tie token lifetime to session lifetime and implement `automaticSilentRenew` for OIDC or re-authenticate via SAML when the session expires. [src5]
- **Not checking `amr` (Authentication Methods References)**: SP accepts any authentication method when it requires MFA. Fix: check the `amr` claim in OIDC ID tokens or the `AuthnContext` in SAML assertions to enforce MFA policies. [src3]

## Diagnostic Commands

```bash
# Decode a SAML response (base64-encoded POST body)
echo "$SAML_RESPONSE" | base64 -d | xmllint --format -

# Validate XML signature in a SAML assertion
xmlsec1 --verify --pubkey-cert-pem idp-cert.pem --id-attr:ID urn:oasis:names:tc:SAML:2.0:assertion:Assertion saml-response.xml

# Fetch OIDC Discovery document
curl -s "https://your-org.okta.com/.well-known/openid-configuration" | jq .

# Fetch OIDC provider's signing keys (JWKS)
curl -s "https://your-org.okta.com/oauth2/default/v1/keys" | jq '.keys[] | {kid, kty, alg}'

# Decode a JWT ID token (without verification — for debugging only)
echo "$ID_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

# Check SAML metadata endpoints
curl -s "https://your-org.okta.com/app/YOUR_APP_ID/sso/saml/metadata" | xmllint --format -

# Test OIDC token endpoint connectivity
curl -X POST "https://your-org.okta.com/oauth2/default/v1/token" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=openid"

# Verify certificate expiry dates
openssl x509 -in idp-cert.pem -noout -dates
openssl x509 -in sp-cert.pem -noout -dates
```

## Version History & Compatibility

| Specification | Version | Status | Key Changes | Date |
|---|---|---|---|---|
| SAML | 2.0 | Current (stable) | Full spec: assertions, bindings, profiles, metadata | 2005-03 |
| SAML | 1.1 | Deprecated | Limited bindings, no SLO, simpler assertions | 2003-09 |
| OpenID Connect | 1.0 | Current (stable) | Core, Discovery, Dynamic Registration, Session Management | 2014-02 |
| OIDC Federation | 1.0 | Draft | Trust chains without bilateral metadata exchange | 2024-ongoing |
| OAuth 2.0 | RFC 6749 | Current (foundation for OIDC) | Authorization framework — OIDC adds identity layer | 2012-10 |
| OAuth 2.1 | Draft | Draft (consolidation) | Merges PKCE, deprecates implicit flow | 2024-ongoing |

### Library Compatibility

| Library | Language | Protocol | Min Runtime | Latest Stable |
|---|---|---|---|---|
| `@node-saml/passport-saml` | Node.js | SAML 2.0 | Node 18+ | 5.x |
| `python3-saml` | Python | SAML 2.0 | Python 3.8+ | 1.16.x |
| `openid-client` | Node.js | OIDC | Node 18+ | 6.x |
| `oidc-client-ts` | TypeScript | OIDC | ES2015+ browsers | 3.x |
| `go-oidc` | Go | OIDC | Go 1.21+ | 3.11.x |
| `Spring Security SAML` | Java | SAML 2.0 | Java 17+ | 6.x (Spring Security) |
| `Spring Security OAuth2` | Java | OIDC | Java 17+ | 6.x (Spring Security) |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Enterprise customers require SSO with their corporate IdP | Only need API-to-API authentication (no user identity) | OAuth 2.0 Client Credentials flow |
| Building multi-tenant SaaS where each tenant has its own IdP | Simple username/password login for a single-tenant app | Session-based auth with bcrypt |
| Regulatory compliance requires federated identity (SOC 2, HIPAA) | Social login only (Google, GitHub, Facebook) | OAuth 2.0 Authorization Code with PKCE |
| Integrating with legacy enterprise systems (AD FS, Shibboleth) | Internal microservice-to-microservice auth | mTLS or JWT service tokens |
| Need Single Logout across multiple applications | Mobile app with biometric login as primary auth | Platform native auth (Face ID, fingerprint) |
| Customer's IT department mandates SAML specifically | Real-time API authorization decisions | OAuth 2.0 token introspection or policy engines |

## Important Caveats

- SAML 2.0 and OIDC solve the **authentication** problem (who is this user?) — they do not solve **authorization** (what can this user do?). Authorization requires a separate layer (RBAC, ABAC, policy engines).
- SAML certificate rotation requires coordinated updates between SP and IdP. Plan rotation 30 days before expiry and support dual certificates during the transition window.
- OIDC `client_secret` should never be embedded in SPAs or mobile apps — use PKCE with a public client instead.
- Some IdPs have proprietary extensions or non-standard claim names. Test with your specific IdP; don't assume standard-compliant behavior.
- SAML SLO (Single Logout) is notoriously unreliable in practice because it requires all SPs to respond to logout requests synchronously. Consider implementing independent session timeouts as a fallback.
- When bridging SAML and OIDC (e.g., your app speaks OIDC but the customer's IdP only speaks SAML), use an identity broker (Keycloak, Auth0, Azure AD) rather than building the bridge yourself.
- XML-based SAML libraries are vulnerable to XXE, XSW, and Billion Laughs attacks if the XML parser is not properly hardened. Always use libraries that are actively maintained and have been audited.

## Related Units

- [OAuth 2.0 Authorization Code with PKCE](/software/patterns/oauth2-authorization-code-pkce/2026)
- [JWT Implementation and Validation](/software/patterns/jwt-implementation/2026)
- [Session Management Patterns](/software/patterns/session-management/2026)
- [Role-Based Access Control Implementation](/software/patterns/rbac-implementation/2026)
