| 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
const passport = require('passport');
const { Strategy } = require('@node-saml/passport-saml');
const fs = require('fs');
const samlStrategy = new Strategy(
{
entryPoint: 'https://idp.example.com/sso',
issuer: 'https://ourapp.com',
callbackURL: 'https://ourapp.com/auth/saml/callback',
cert: fs.readFileSync('./certs/idp-public.pem', 'utf-8'),
validateInResponseTo: true,
wantAssertionsSigned: true,
wantAuthnResponseSigned: true,
decryptionPvk: fs.readFileSync('./certs/sp-private.pem', 'utf-8'),
identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
},
(profile, done) => {
.(, profile);
user = {
: profile.,
: profile..,
: profile..,
: profile.. || [],
};
(, user);
}
);
passport.(, samlStrategy);
app.(, passport.(, {
: ,
}));
app.(, {
passport.(, {
(err || !user) {
res.();
}
req.(user, {
(err) (err);
res.();
});
})(req, res, next);
});
app.(, {
metadata = samlStrategy.(
,
);
res.().(metadata);
});
app.(, {
(!req.) {
res.();
}
options = {
: ,
: ,
};
samlStrategy.(req, {
(err) res.().(err);
req.( {
(err) res.().(err);
res.(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() {
this.issuer = await Issuer.discover(this.config.issuerUrl);
this.client = new this.issuer.Client({
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
redirect_uris: [this..],
: [],
});
response = ();
. = response.();
}
() {
decoded = jwt.(idToken, { : });
(!decoded) {
();
}
{ header, payload } = decoded;
jwk = ...( key. === header.);
(!jwk) {
();
}
publicKey = .(jwk);
{
verified = jwt.(idToken, publicKey, {
: [],
: ..,
: ..,
});
verified;
} (error) {
();
}
}
() {
decoded = jwt.(accessToken, { : });
(!decoded) {
();
}
{ payload } = decoded;
now = .(.() / );
(payload. <= now) {
();
}
(payload. !== ..) {
();
}
payload;
}
() {
}
}
oidcValidator = ({
: ,
: ,
: ,
: ,
});
oidcValidator.();
app.( {
authHeader = req..;
(!authHeader) {
res.().({ : });
}
token = authHeader.(, );
{
req. = oidcValidator.(token);
();
} (error) {
res.().({ : error. });
}
});
Pattern 3: SCIM 2.0 User Provisioning
class SCIMUserProvisioner {
constructor(config) {
this.config = config;
}
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.(externalId);
:
();
}
}
() {
(!attributes. || !attributes.) {
();
}
user = db..({
: attributes.,
: attributes.,
: attributes.,
: attributes.,
: attributes.,
: attributes. ?? ,
: attributes. || [],
});
user;
}
() {
user = db..(externalId);
(!user) {
();
}
updated = db..(user., {
: attributes.,
: attributes.,
: attributes. || [],
});
updated;
}
() {
user = db..(externalId);
(!user) {
();
}
db..(user., { : });
{ : };
}
() {
}
}
app.(, (req, res) => {
{
(!(req)) {
res.().({ : });
}
provisioner = (config);
result = provisioner.(req.);
res.(result);
} (error) {
.(, error);
res.().({ : error. });
}
});
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);
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).({ : });
}
};
}
}
jwtMiddleware = (publicKey);
app.(, jwtMiddleware.());
app.(, {
res.({
: ,
: req..,
});
});
Level 3: Advanced
Advanced: Context7 MCP Integration
const { Context7Client } = require('context7-mcp');
class IdentityThreatIntelligence {
constructor(apiKey) {
this.context7 = new Context7Client(apiKey);
}
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,
};
}
async analyzeProvisioningEvent(event) {
const analysis = await this.context7.query({
type: 'provisioning_anomaly',
: event.,
: event.,
: event.,
});
(analysis.) {
.(, analysis);
}
analysis;
}
}
Checklist
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 |