Back to Skills

moai-security-identity

modu-ai
Updated 3 days ago
21 views
424
78
424
View on GitHub
Metaai

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 CommandRecommended
/plugin add https://github.com/modu-ai/moai-adk
Git CloneAlternative
git clone https://github.com/modu-ai/moai-adk.git ~/.claude/skills/moai-security-identity

Copy 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

FeatureImplementation
SAML@node-saml/passport-saml 3.2.4+
OIDCopenid-client (npm)
JWTjsonwebtoken (npm)
SCIMCustom webhook handler
MonitoringContext7 MCP

GitHub Repository

modu-ai/moai-adk
Path: .claude/skills/moai-security-identity
agentic-aiagentic-codingagentic-workflowclaudeclaudecodevibe-coding

Related Skills

sglang

Meta

SGLang 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.

View skill

evaluating-llms-harness

Testing

This 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.

View skill

llamaguard

Other

LlamaGuard 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.

View skill

langchain

Meta

LangChain 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.

View skill