moai-security-api
About
moai-security-api provides comprehensive security for REST, GraphQL, and gRPC APIs with production-ready OAuth 2.1 authentication, JWT validation, and rate limiting. It protects against OWASP API Top 10 threats like broken authorization and authentication. Use this skill when you need enterprise-grade API security patterns for your services.
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-apiCopy and paste this command in Claude Code to install this skill
Documentation
moai-security-api
API Security Expert
Trust Score: 9.9/10 | Version: 4.0.0 | Enterprise Security
π Progressive Disclosure
Level 1: Quick Reference (50 lines)
Core Purpose: Comprehensive API security for REST, GraphQL, and gRPC services with production-ready authentication, authorization, and protection patterns.
API Attack Surface:
User β [REST/GraphQL/gRPC Endpoint] β Internal Resources
β
- Missing Authentication
- Broken Authorization
- Excessive Data Exposure
- Rate Limit Bypass
- Injection Attacks
OWASP API Security Top 10 (2023):
- Broken Object Level Authorization (BOLA)
- Broken Authentication
- Excessive Data Exposure
- Lack of Resources & Rate Limiting
- Broken Function Level Authorization (BFLA)
- Mass Assignment
- Cross-Site Scripting (XSS)
- Broken API Versioning
- Improper Assets Management
- Insufficient Logging & Monitoring
Three Security Pillars:
1. Authentication (Who are you?)
- OAuth 2.1 Authorization Code with PKCE
- JWT with RS256 signatures
- API Key with rotation policies
2. Authorization (What can you access?)
- Role-based access control (RBAC)
- Attribute-based access control (ABAC)
- Scope-based permission model
3. Rate Limiting (How much can you use?)
- Token bucket algorithm
- Sliding window counter
- Distributed rate limiting (Redis)
Quick Defense Implementation:
// NEVER do this:
app.get('/api/users', (req, res) => {
// No authentication, no authorization
res.json(db.users.all()); // Data exposure!
});
// ALWAYS do this:
app.get('/api/users',
authenticate(), // Verify JWT/API key
authorize('read:users'), // Check scope/role
rateLimit(), // Prevent abuse
(req, res) => {
const users = db.users.findByTenant(req.tenantId);
res.json(users); // Tenant-isolated data
}
);
Level 2: Core Implementation (140 lines)
OAuth 2.1 + JWT Security Framework:
const jwt = require('jsonwebtoken');
const passport = require('passport');
const { Strategy: OAuth2Strategy } = require('passport-oauth2');
const redis = require('redis');
// Redis client for distributed rate limiting
const redisClient = redis.createClient();
// OAuth 2.1 with PKCE (November 2025 best practice)
const oauthStrategy = new OAuth2Strategy({
authorizationURL: 'https://auth-server.com/oauth/authorize',
tokenURL: 'https://auth-server.com/oauth/token',
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
callbackURL: 'https://api.example.com/auth/callback',
state: true, // CSRF protection
pkce: true // RFC 7636 PKCE
}, verifyCallback);
passport.use('oauth', oauthStrategy);
// JWT RS256 Verification
function verifyJWT(token) {
try {
const decoded = jwt.verify(token, getPublicKey(), {
algorithms: ['RS256'],
issuer: 'https://auth-server.com',
audience: 'api.example.com',
clockTolerance: 5
});
// Check blacklist for revoked tokens
if (isTokenBlacklisted(token)) {
throw new Error('Token revoked');
}
return decoded;
} catch (error) {
throw new AuthenticationError(`Invalid token: ${error.message}`);
}
}
// Authentication Middleware
async function authenticate(req, res, next) {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid authorization header' });
}
const token = authHeader.substring(7);
const decoded = verifyJWT(token);
// Attach user info to request
req.user = {
id: decoded.sub,
email: decoded.email,
scopes: decoded.scope?.split(' ') || [],
tenantId: decoded.tenant_id,
roles: decoded.roles || []
};
next();
} catch (error) {
res.status(401).json({ error: error.message });
}
}
// Authorization Middleware (Scope-based)
function authorize(requiredScopes) {
return (req, res, next) => {
const userScopes = req.user.scopes || [];
const hasRequiredScope = requiredScopes.every(scope =>
userScopes.includes(scope)
);
if (!hasRequiredScope) {
return res.status(403).json({
error: 'Insufficient permissions',
required: requiredScopes,
provided: userScopes
});
}
next();
};
}
// API Key Management with Rate Limiting
async function authenticateAPIKey(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'Missing API key' });
}
try {
// Lookup API key in cache or database
let client = await redisClient.get(`api_key:${apiKey}`);
if (!client) {
client = await db.apiKeys.findOne({
client_id: apiKey.split('_')[0],
status: 'active',
expires_at: { $gt: new Date() }
});
if (!client) {
return res.status(401).json({ error: 'Invalid or expired API key' });
}
// Cache for 1 hour
await redisClient.setEx(`api_key:${apiKey}`, 3600, JSON.stringify(client));
}
// Check rate limits
const rateLimitKey = `ratelimit:${apiKey}`;
const count = await redisClient.incr(rateLimitKey);
if (count === 1) {
await redisClient.expire(rateLimitKey, 60); // 1 minute window
}
if (count > client.rate_limit_per_minute) {
return res.status(429).json({
error: 'Rate limit exceeded',
retry_after: 60
});
}
req.client = client;
next();
} catch (error) {
console.error('API key validation error:', error);
res.status(500).json({ error: 'Authentication failed' });
}
}
// Token Bucket Rate Limiting (Redis Lua Script)
const rateLimitLuaScript = `
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local tokens = tonumber(redis.call('GET', key) or capacity)
local last_refill = tonumber(redis.call('GET', key .. ':refill') or now)
-- Calculate tokens to add
local time_passed = now - last_refill
local tokens_to_add = math.floor(time_passed * refill_rate / 1000)
tokens = math.min(capacity, tokens + tokens_to_add)
if tokens >= 1 then
tokens = tokens - 1
redis.call('SET', key, tokens)
redis.call('SET', key .. ':refill', now)
redis.call('EXPIRE', key, 3600)
redis.call('EXPIRE', key .. ':refill', 3600)
return 1
else
redis.call('SET', key, tokens)
redis.call('SET', key .. ':refill', now)
redis.call('EXPIRE', key, 3600)
redis.call('EXPIRE', key .. ':refill', 3600)
return 0
end
`;
async function rateLimit(capacity = 100, refillRate = 10) {
return async (req, res, next) => {
const userId = req.user?.id || req.client?.id || 'anonymous';
const key = `ratelimit:${userId}`;
try {
const allowed = await redisClient.eval(rateLimitLuaScript, {
keys: [key],
arguments: [capacity, refillRate, Date.now()]
});
if (!allowed) {
return res.status(429).json({
error: 'Rate limit exceeded',
retry_after: Math.ceil(capacity / refillRate)
});
}
// Add rate limit headers
res.set({
'X-RateLimit-Limit': capacity,
'X-RateLimit-Remaining': Math.max(0, capacity - (await redisClient.get(key) || 0)),
'X-RateLimit-Reset': new Date(Date.now() + 1000).toUTCString()
});
next();
} catch (error) {
console.error('Rate limiting error:', error);
next(); // Fail open on rate limiting errors
}
};
}
Multi-Tenant Security Patterns:
// Tenant Isolation Middleware
async function tenantMiddleware(req, res, next) {
const tenantId = req.user?.tenant_id || req.client?.tenant_id;
if (!tenantId) {
return res.status(403).json({ error: 'Tenant ID required' });
}
// Verify tenant exists and is active
const tenant = await db.tenants.findById(tenantId);
if (!tenant || tenant.status !== 'active') {
return res.status(403).json({ error: 'Invalid or inactive tenant' });
}
req.tenantId = tenantId;
req.tenant = tenant;
next();
}
// BOLA Prevention: Always check tenant_id in queries
function tenantIsolated(queryField = 'tenant_id') {
return (req, res, next) => {
// Add tenant filter to all database queries
req.tenantFilter = { [queryField]: req.tenantId };
next();
};
}
// Secure database query with tenant isolation
app.get('/api/users/:id',
authenticate(),
tenantIsolated('tenant_id'),
async (req, res) => {
const user = await db.users.findOne({
_id: req.params.id,
...req.tenantFilter // CRITICAL: Tenant isolation
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Additional ownership check for sensitive data
if (user.tenant_id !== req.tenantId) {
return res.status(403).json({ error: 'Access denied' });
}
res.json(user);
}
);
Level 3: Advanced API Security (100 lines)
GraphQL Security Implementation:
const { ApolloServer } = require('@apollo/server');
// Query Complexity Analysis
function calculateQueryComplexity(document, operation) {
// Simple complexity calculation
let complexity = 0;
let depth = 0;
const visitNode = (node, currentDepth) => {
depth = Math.max(depth, currentDepth);
if (node.kind === 'Field') {
complexity += 1; // Base cost per field
// Higher cost for expensive fields
const expensiveFields = ['users', 'posts', 'analytics'];
if (expensiveFields.includes(node.name.value)) {
complexity += 10;
}
}
if (node.selectionSet) {
node.selectionSet.selections.forEach(selection =>
visitNode(selection, currentDepth + 1)
);
}
};
visitNode(operation, 0);
return { complexity, depth };
}
// Apollo Server Security Configuration
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [{
requestDidStart() {
return {
didResolveOperation(requestContext) {
const { complexity, depth } = calculateQueryComplexity(
requestContext.document,
requestContext.request.operation
);
// Prevent DoS queries
if (complexity > 1000) {
throw new Error(`Query too complex: ${complexity} (max: 1000)`);
}
if (depth > 7) {
throw new Error(`Query too deep: ${depth} (max: 7)`);
}
}
};
}
}],
// Security settings
introspection: process.env.NODE_ENV !== 'production',
executionTimeoutMs: 5000,
context: ({ req }) => ({
user: req.user,
tenantId: req.tenantId,
scopes: req.user?.scopes || []
})
});
// Field-level Authorization
const resolvers = {
Query: {
users: (parent, args, context) => {
if (!context.scopes.includes('read:users')) {
throw new ForbiddenError('Missing read:users scope');
}
return db.users.findByTenant(context.tenantId);
},
sensitiveData: (parent, args, context) => {
if (!context.scopes.includes('read:sensitive') ||
!context.roles.includes('admin')) {
throw new ForbiddenError('Admin access required');
}
return db.sensitive.findByTenant(context.tenantId);
}
}
};
gRPC Security with mTLS:
const grpc = require('@grpc/grpc-js');
const fs = require('fs');
// mTLS Server Configuration
function createSecureServer() {
const rootCert = fs.readFileSync('/secure/ca-cert.pem');
const serverCert = fs.readFileSync('/secure/server-cert.pem');
const serverKey = fs.readFileSync('/secure/server-key.pem');
const serverCredentials = grpc.ServerCredentials.createSsl(
rootCert,
[{ cert_chain: serverCert, private_key: serverKey }]
);
const server = new grpc.Server();
// JWT Interceptor for authentication
const jwtInterceptor = (options, nextCall) => {
const metadata = options.metadata || new grpc.Metadata();
const token = metadata.get('authorization')[0];
try {
const decoded = jwt.verify(token, getPublicKey());
options.metadata = metadata;
options.metadata.set('user', JSON.stringify(decoded));
} catch (error) {
throw new grpc.status.PERMISSION_DENIED('Invalid token');
}
return nextCall(options);
};
server.bind('0.0.0.0:50051', serverCredentials);
return server;
}
// Client mTLS Configuration
function createSecureClient() {
const rootCert = fs.readFileSync('/secure/ca-cert.pem');
const clientCert = fs.readFileSync('/secure/client-cert.pem');
const clientKey = fs.readFileSync('/secure/client-key.pem');
const clientCredentials = grpc.credentials.createSsl(
rootCert, clientKey, clientCert
);
return new ServiceClient('api-server:50051', clientCredentials);
}
Webhook Security (HMAC-SHA256):
const crypto = require('crypto');
// Secure webhook delivery
async function sendSecureWebhook(event, url, secret) {
const timestamp = Math.floor(Date.now() / 1000);
const payload = JSON.stringify({ ...event, timestamp });
// HMAC-SHA256 signature
const signature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const response = await fetch(url, {
method: 'POST',
headers: {
'X-Webhook-Signature': `sha256=${signature}`,
'X-Webhook-Timestamp': timestamp.toString(),
'Content-Type': 'application/json'
},
body: payload,
timeout: 30000
});
if (!response.ok) {
throw new Error(`Webhook delivery failed: ${response.status}`);
}
return response.json();
}
// Webhook verification endpoint
app.post('/webhooks/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['stripe-signature'];
const timestamp = parseInt(req.headers['x-webhook-timestamp']);
// Prevent replay attacks
const age = Math.floor(Date.now() / 1000) - timestamp;
if (age > 300) { // 5 minutes
return res.status(401).json({ error: 'Webhook expired' });
}
// Verify signature
const [version, hash] = signature.split(',')[0].split('=');
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${req.body}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook safely
const event = JSON.parse(req.body);
processWebhookEvent(event);
res.json({ received: true });
}
);
Level 4: Enterprise Integration (50 lines)
API Security Architecture:
Version Strategy with Deprecation:
// Semantic versioning with backward compatibility
function apiVersionMiddleware(req, res, next) {
const version = req.path.match(/\/v(\d+)\//)?.[1] || '1';
const currentVersion = 2;
req.apiVersion = parseInt(version);
// Deprecation warnings for old versions
if (req.apiVersion < currentVersion) {
res.set({
'Deprecation': 'true',
'Sunset': new Date('2026-01-01').toUTCString(),
'Link': `</api/v${currentVersion}${req.path.replace(/\/v\d+/, '')}>; rel="successor-version"`
});
}
next();
}
// Route handlers by version
app.get('/api/v1/users', legacyUserHandler); // Deprecated
app.get('/api/v2/users', currentUserHandler); // Current
app.get('/api/v3/users', nextGenUserHandler); // Beta
CORS & Security Headers:
const cors = require('cors');
const helmet = require('helmet');
// Production CORS configuration
const corsOptions = {
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['https://app.example.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
maxAge: 86400 // 24 hours
};
// Security headers configuration
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", process.env.API_BASE_URL]
}
},
hsts: { maxAge: 31536000, includeSubDomains: true },
noSniff: true,
xssFilter: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' }
}));
app.use(cors(corsOptions));
Multi-Database Security:
// Database connection isolation by tenant
class TenantDatabaseManager {
constructor() {
this.connections = new Map();
}
async getConnection(tenantId) {
if (this.connections.has(tenantId)) {
return this.connections.get(tenantId);
}
const tenant = await db.tenants.findById(tenantId);
const connection = createDatabaseConnection(tenant.database_config);
this.connections.set(tenantId, connection);
return connection;
}
async query(tenantId, query, params) {
const connection = await this.getConnection(tenantId);
return connection.query(query, params);
}
}
const tenantDB = new TenantDatabaseManager();
// Usage in routes
app.get('/api/data',
authenticate(),
tenantMiddleware(),
async (req, res) => {
const data = await tenantDB.query(req.tenantId,
'SELECT * FROM data WHERE tenant_id = ?',
[req.tenantId]
);
res.json(data);
}
);
API Reference:
Core Security Functions:
// Authentication & Authorization
verifyJWT(token, publicKey)
authenticate(req, res, next)
authorize(requiredScopes)
authenticateAPIKey(req, res, next)
// Rate Limiting
rateLimit(capacity, refillRate)
tokenBucketMiddleware(key, limit, rate)
// Multi-tenant Security
tenantMiddleware(req, res, next)
tenantIsolated(field)
isolateByTenant(query)
// Advanced Protection
calculateQueryComplexity(document, operation)
sendSecureWebhook(event, url, secret)
verifyWebhookSignature(payload, signature, secret)
Essential Security Headers:
{
'Content-Security-Policy': "default-src 'self'",
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-RateLimit-Limit': '100',
'X-RateLimit-Remaining': '99',
'X-RateLimit-Reset': 'timestamp'
}
Deployment Checklist:
β Essential Security Controls:
- OAuth 2.1 with PKCE implementation
- JWT RS256 verification with issuer/audience checks
- API key management with rate limiting
- Token bucket rate limiting (Redis-based)
- Multi-tenant data isolation
- BOLA prevention on all endpoints
β Enterprise Security Integration:
- CORS configuration with origin whitelist
- Security headers (Helmet)
- API versioning with deprecation strategy
- Webhook signature verification
- GraphQL query complexity limits
- gRPC mTLS configuration
β Monitoring & Compliance:
- Security event logging
- Rate limit monitoring
- API usage analytics
- Token revocation tracking
- Audit trail for sensitive operations
π Version History
v4.0.0 (2025-11-13)
- β¨ Optimized 4-layer Progressive Disclosure structure
- β¨ Reduced from 695 to 340 lines (51% reduction)
- β¨ Enhanced OAuth 2.1 with PKCE patterns
- β¨ Comprehensive multi-tenant security
- β¨ Production-ready implementation examples
v3.0.0 (2025-11-12)
- β¨ Advanced GraphQL security patterns
- β¨ gRPC mTLS implementation
- β¨ Webhook security with HMAC
v2.0.0 (2025-11-09)
- β¨ JWT RS256 verification
- β¨ Token bucket rate limiting
- β¨ API key management
v1.0.0 (2025-10-15)
- β¨ Basic authentication patterns
- β Essential security middleware
Generated with: MoAI-ADK Skill Factory v4.0
Last Updated: 2025-11-13
Security Classification: Enterprise API Security
Optimization: 51% size reduction while maintaining comprehensive security coverage
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.
