install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/moai-security-identity" ~/.claude/skills/diegosouzapw-awesome-omni-skill-moai-security-identity-e109bd && rm -rf "$T"
manifest:
skills/development/moai-security-identity/SKILL.mdsource content
moai-security-identity: SAML 2.0 & OIDC Identity Management
Enterprise SSO with SAML 2.0, OpenID Connect & OAuth 2.0
Trust Score: 9.9/10 | Version: 4.0.0 | Enterprise Mode | Last Updated: 2025-11-12
Overview
Identity and Access Management (IAM) for enterprise applications using SAML 2.0 for legacy systems and OpenID Connect (OIDC) for modern APIs. 2025 trend: 72% of enterprises now adopt multi-protocol SSO. This Skill covers SAML assertion validation, OIDC token processing, JWT verification, JIT provisioning, and SCIM 2.0 user synchronization.
When to use this Skill:
- Implementing enterprise Single Sign-On (SSO)
- Supporting multiple identity protocols (SAML + OIDC)
- Integrating Auth0, Keycloak, or Okta
- SAML/OIDC federation between organizations
- User provisioning automation (SCIM)
- Legacy B2B SAML + modern API OIDC
Level 1: Foundations
SAML vs OIDC Comparison
SAML 2.0 (XML-based, legacy enterprise): ├─ Protocol: XML assertions over HTTP POST/Redirect ├─ Use: Legacy web applications, B2B federation ├─ Complexity: Higher (XML parsing, certificates) ├─ Token Format: SAML Assertions (XML) └─ Adoption: Enterprise (Salesforce, SharePoint, SAP) OIDC (JSON-based, modern APIs): ├─ Protocol: Built on OAuth 2.0, REST APIs ├─ Use: Modern web/mobile apps, microservices ├─ Complexity: Lower (JSON, standard OAuth) ├─ Token Format: JWT (JSON Web Tokens) └─ Adoption: Modern (mobile, SPA, APIs) Best Practice (2025): - Legacy B2B apps: SAML 2.0 - Modern APIs: OIDC - Hybrid enterprises: Both (via federation)
SAML 2.0 Flow
1. User clicks "Login with Company SSO" ↓ 2. Service Provider (SP) → Identity Provider (IdP) Sends: AuthnRequest (signed, encrypted) ↓ 3. User authenticates at IdP (username/password) ↓ 4. IdP → Service Provider (SAML Response) Contains: SAML Assertion (signed, encrypted) ├─ NameID (user identifier) ├─ Attributes (email, groups, roles) └─ AuthnStatement (authentication confirmation) ↓ 5. SP verifies signature, creates session ↓ 6. User logged in to SP
OIDC Flow
1. User clicks "Login with Google" ↓ 2. SPA → Authorization Server Sends: authorization request (client_id, redirect_uri) ↓ 3. User authenticates at Authorization Server ↓ 4. Authorization Server → SPA (authorization code) ↓ 5. SPA backend → Authorization Server (token exchange) Sends: authorization code, client_secret ↓ 6. Authorization Server → SPA backend Returns: ID Token (JWT), Access Token, Refresh Token ↓ 7. SPA backend creates session, user logged in
Level 2: Implementation Patterns
Pattern 1: SAML 2.0 Assertion Validation
const passport = require('passport'); const { Strategy } = require('@node-saml/passport-saml'); const fs = require('fs'); const samlStrategy = new Strategy( { // Service Provider (our app) metadata entryPoint: 'https://idp.example.com/sso', // IdP's SSO endpoint issuer: 'https://ourapp.com', callbackURL: 'https://ourapp.com/auth/saml/callback', // Certificates for signature verification cert: fs.readFileSync('./certs/idp-public.pem', 'utf-8'), // Security settings validateInResponseTo: true, wantAssertionsSigned: true, // Require signed assertions wantAuthnResponseSigned: true, // Require signed response // Encryption decryptionPvk: fs.readFileSync('./certs/sp-private.pem', 'utf-8'), // Identifier format identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', }, (profile, done) => { // profile contains: // - nameID: unique user identifier // - nameIDFormat: format of identifier // - sessionIndex: session index // - attributes: user attributes from IdP console.log('SAML Profile:', profile); // Find or create user const user = { id: profile.nameID, email: profile.attributes.email, name: profile.attributes.displayName, groups: profile.attributes.groups || [], }; done(null, user); } ); passport.use('saml', samlStrategy); // Express routes app.get('/auth/saml', passport.authenticate('saml', { failureRedirect: '/login', })); app.post('/auth/saml/callback', (req, res, next) => { passport.authenticate('saml', (err, user) => { if (err || !user) { return res.redirect('/login?error=authentication_failed'); } // Create session req.logIn(user, (err) => { if (err) return next(err); res.redirect('/dashboard'); }); })(req, res, next); }); app.get('/auth/saml/metadata', (req, res) => { // Provide SP metadata for IdP to consume const metadata = samlStrategy.generateServiceProviderMetadata( null, // decryption certificate null // encryption certificate ); res.type('application/xml').send(metadata); }); // Logout (SAML SLO) app.get('/logout', (req, res) => { if (!req.user) { return res.redirect('/'); } const options = { destination: 'https://idp.example.com/slo', // IdP's SLO endpoint issuer: 'https://ourapp.com', }; samlStrategy.logout(req, (err, url) => { if (err) return res.status(500).send(err); req.logOut((err) => { if (err) return res.status(500).send(err); res.redirect(url); }); }); });
Pattern 2: OIDC Token Validation
const { Issuer } = require('openid-client'); const jwt = require('jsonwebtoken'); class OIDCValidator { constructor(config) { this.config = config; this.issuer = null; this.client = null; this.jwks = null; } async initialize() { // Discover OIDC provider configuration this.issuer = await Issuer.discover(this.config.issuerUrl); // Create client this.client = new this.issuer.Client({ client_id: this.config.clientId, client_secret: this.config.clientSecret, redirect_uris: [this.config.redirectUri], response_types: ['code'], }); // Cache JWKS (JSON Web Key Set) const response = await fetch(`${this.config.issuerUrl}/.well-known/jwks.json`); this.jwks = await response.json(); } // Validate ID Token signature validateIdToken(idToken) { const decoded = jwt.decode(idToken, { complete: true }); if (!decoded) { throw new Error('Invalid token format'); } const { header, payload } = decoded; // 1. Find public key matching kid const jwk = this.jwks.keys.find(key => key.kid === header.kid); if (!jwk) { throw new Error('Key not found in JWKS'); } // 2. Convert JWK to PEM const publicKey = this.jwkToPem(jwk); // 3. Verify signature try { const verified = jwt.verify(idToken, publicKey, { algorithms: ['RS256'], // Ensure RS256 issuer: this.config.issuerUrl, audience: this.config.clientId, }); return verified; } catch (error) { throw new Error(`Token verification failed: ${error.message}`); } } // Validate Access Token validateAccessToken(accessToken) { const decoded = jwt.decode(accessToken, { complete: true }); if (!decoded) { throw new Error('Invalid token format'); } // 1. Check expiration const { payload } = decoded; const now = Math.floor(Date.now() / 1000); if (payload.exp <= now) { throw new Error('Token expired'); } // 2. Check issuer if (payload.iss !== this.config.issuerUrl) { throw new Error('Invalid issuer'); } return payload; } // Convert JWK to PEM format jwkToPem(jwk) { // Implementation using node-jose or similar // Returns PEM-formatted public key // ... (crypto conversion logic) } } // Usage const oidcValidator = new OIDCValidator({ issuerUrl: 'https://auth.example.com', clientId: 'my-app-id', clientSecret: 'my-secret', redirectUri: 'https://myapp.com/auth/callback', }); await oidcValidator.initialize(); // In middleware app.use((req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader) { return res.status(401).json({ error: 'Missing authorization' }); } const token = authHeader.replace('Bearer ', ''); try { req.user = oidcValidator.validateIdToken(token); next(); } catch (error) { res.status(401).json({ error: error.message }); } });
Pattern 3: SCIM 2.0 User Provisioning
class SCIMUserProvisioner { constructor(config) { this.config = config; } // Handle SCIM provisioning webhook from IdP handleScimWebhook(scimEvent) { switch (scimEvent.resourceType) { case 'User': return this.handleUserEvent(scimEvent); case 'Group': return this.handleGroupEvent(scimEvent); default: throw new Error(`Unknown resource type: ${scimEvent.resourceType}`); } } async handleUserEvent(event) { const { externalId, attributes } = event; switch (event.eventType) { case 'user.created': return this.createUser(attributes); case 'user.updated': return this.updateUser(externalId, attributes); case 'user.deleted': return this.deleteUser(externalId); default: throw new Error(`Unknown event: ${event.eventType}`); } } async createUser(attributes) { // Validate required fields if (!attributes.email || !attributes.userName) { throw new Error('Missing required fields'); } // Create database record const user = await db.users.create({ externalId: attributes.externalId, email: attributes.email, displayName: attributes.displayName, givenName: attributes.givenName, familyName: attributes.familyName, active: attributes.active ?? true, groups: attributes.groups || [], }); return user; } async updateUser(externalId, attributes) { const user = await db.users.findByExternalId(externalId); if (!user) { throw new Error(`User not found: ${externalId}`); } // Update fields const updated = await db.users.update(user.id, { displayName: attributes.displayName, active: attributes.active, groups: attributes.groups || [], }); return updated; } async deleteUser(externalId) { const user = await db.users.findByExternalId(externalId); if (!user) { throw new Error(`User not found: ${externalId}`); } // Soft delete (mark as inactive) await db.users.update(user.id, { active: false }); return { success: true }; } async handleGroupEvent(event) { // Similar pattern for group provisioning // Handle group.created, group.updated, group.deleted } } // Express endpoint for SCIM webhooks app.post('/scim/webhook', async (req, res) => { try { // Verify webhook signature if (!verifyWebhookSignature(req)) { return res.status(401).json({ error: 'Invalid signature' }); } const provisioner = new SCIMUserProvisioner(config); const result = await provisioner.handleScimWebhook(req.body); res.json(result); } catch (error) { console.error('SCIM webhook error:', error); res.status(400).json({ error: error.message }); } });
Pattern 4: JWT Bearer Token in APIs
class JWTMiddleware { constructor(publicKey) { this.publicKey = publicKey; } middleware() { return (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Missing token' }); } const token = authHeader.slice(7); // Remove "Bearer " try { const payload = jwt.verify(token, this.publicKey, { algorithms: ['RS256'], }); req.user = { id: payload.sub, email: payload.email, scope: payload.scope ? payload.scope.split(' ') : [], }; next(); } catch (error) { res.status(401).json({ error: 'Invalid token' }); } }; } } // Usage const jwtMiddleware = new JWTMiddleware(publicKey); app.use('/api', jwtMiddleware.middleware()); app.get('/api/protected', (req, res) => { res.json({ message: `Hello, ${req.user.email}`, scopes: req.user.scope, }); });
Level 3: Advanced
Advanced: Context7 MCP Integration
const { Context7Client } = require('context7-mcp'); class IdentityThreatIntelligence { constructor(apiKey) { this.context7 = new Context7Client(apiKey); } // Check user identity against threat intelligence async validateUserIdentity(user) { const threats = await this.context7.query({ type: 'identity_threat', email: user.email, externalId: user.externalId, tags: ['fraud', 'compromise', 'insider_threat'], }); return { safe: threats.severity === 0, severity: threats.severity, details: threats, }; } // Monitor provisioning events for anomalies async analyzeProvisioningEvent(event) { const analysis = await this.context7.query({ type: 'provisioning_anomaly', eventType: event.eventType, timestamp: event.timestamp, userId: event.externalId, }); if (analysis.anomalous) { console.warn('Anomalous provisioning event detected:', analysis); // Alert security team } return analysis; } }
Checklist
- SAML 2.0 strategy configured with certificate verification
- OIDC provider discovery working
- JWT token signature validation implemented
- Token expiration checks in place
- SCIM webhook handling for user provisioning
- JIT (Just-In-Time) provisioning working
- Multi-protocol SSO (SAML + OIDC) tested
- Identity threat intelligence integrated
- SSO logout (SLO) working
- Performance tested at scale
Quick Reference
| Feature | Implementation |
|---|---|
| SAML | @node-saml/passport-saml 3.2.4+ |
| OIDC | openid-client (npm) |
| JWT | jsonwebtoken (npm) |
| SCIM | Custom webhook handler |
| Monitoring | Context7 MCP |