moai-security-identity
About
This enterprise IAM skill implements multi-protocol SSO using SAML 2.0 and OpenID Connect for secure identity management. It handles SAML assertion validation, OIDC token processing, JWT verification, and SCIM 2.0 user synchronization. Use this skill when integrating enterprise SSO with providers like Auth0 or Okta, or when supporting both legacy SAML and modern OIDC protocols.
Quick Install
Claude Code
Recommended/plugin add https://github.com/modu-ai/moai-adkgit clone https://github.com/modu-ai/moai-adk.git ~/.claude/skills/moai-security-identityCopy and paste this command in Claude Code to install this skill
Documentation
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 |
GitHub Repository
Related Skills
sglang
MetaSGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
llamaguard
OtherLlamaGuard is Meta's 7-8B parameter model for moderating LLM inputs and outputs across six safety categories like violence and hate speech. It offers 94-95% accuracy and can be deployed using vLLM, Hugging Face, or Amazon SageMaker. Use this skill to easily integrate content filtering and safety guardrails into your AI applications.
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
