moai-security-identity
Enterprise Skill for advanced development
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install ajbcoding-claude-skill-eval-moai-security-identity
Repository
Skill path: moai-adk-main/src/moai_adk/templates/.claude/skills/moai-security-identity
Enterprise Skill for advanced development
Open repositoryBest for
Primary workflow: Run DevOps.
Technical facets: Full Stack, Security.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: AJBcoding.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install moai-security-identity into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/AJBcoding/claude-skill-eval before adding moai-security-identity to shared team environments
- Use moai-security-identity for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: "moai-security-identity"
version: "4.0.0"
status: stable
description: "Enterprise Skill for advanced development"
allowed-tools: "Read, Bash, WebSearch, WebFetch"
---
# 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
```javascript
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
```javascript
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
```javascript
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
```javascript
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
```javascript
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 |