API Gateway Security Patterns: Defense in Depth Architecture
April 7, 2026180 min read0 views
API Gateway Security Patterns: Defense in Depth Architecture
API gateways serve as the security perimeter for microservices architectures. Understanding token exchange mechanisms, scope mapping, rate limiting strategies, WAF integration, and zero-trust patterns is essential for building secure API infrastructure.
API Gateway Security Architecture
┌─────────────────────────────────────────────────────────────────────────┐
│ API Gateway Security Layers │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ External Traffic │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ DDoS Protection (L3/L4) │ │
│ │ • SYN flood protection │ │
│ │ • Volumetric attack mitigation │ │
│ │ • IP reputation filtering │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ TLS Termination │ │
│ │ • Certificate validation │ │
│ │ • Protocol enforcement (TLS 1.3) │ │
│ │ • Cipher suite restrictions │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ WAF (Web Application Firewall) │ │
│ │ • OWASP rule sets │ │
│ │ • Custom rule engine │ │
│ │ • Request/response inspection │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ API Gateway Core │ │
│ │ • Rate limiting & throttling │ │
│ │ • Authentication & token validation │ │
│ │ • Authorization & scope enforcement │ │
│ │ • Request transformation │ │
│ │ • Response filtering │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Service Mesh (mTLS) │ │
│ │ • Service-to-service authentication │ │
│ │ • Encrypted internal traffic │ │
│ │ • Policy enforcement │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │Svc A │ │Svc B │ │Svc C │ │Svc D │ │
│ └───────┘ └───────┘ └───────┘ └───────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Token Exchange and Translation
API gateways often need to translate between different token formats and trust domains.
OAuth Token Exchange (RFC 8693)
import { SignJWT, jwtVerify, decodeJwt } from 'jose';
import { createHash, randomBytes } from 'crypto';
interface TokenExchangeRequest {
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange';
subject_token: string;
subject_token_type: string;
requested_token_type?: string;
audience?: string;
scope?: string;
actor_token?: string;
actor_token_type?: string;
}
interface TokenExchangeResponse {
access_token: string;
issued_token_type: string;
token_type: string;
expires_in: number;
scope?: string;
}
class TokenExchangeService {
private readonly signingKey: CryptoKey;
private readonly trustedIssuers: Map<string, TrustedIssuer>;
private readonly audienceMapping: Map<string, AudienceConfig>;
constructor(
signingKey: CryptoKey,
trustedIssuers: Map<string, TrustedIssuer>,
audienceMapping: Map<string, AudienceConfig>
) {
this.signingKey = signingKey;
this.trustedIssuers = trustedIssuers;
this.audienceMapping = audienceMapping;
}
async exchange(
request: TokenExchangeRequest,
clientId: string
): Promise<TokenExchangeResponse> {
// Validate subject token
const subjectClaims = await this.validateSubjectToken(
request.subject_token,
request.subject_token_type
);
// Validate actor token if present (delegation scenario)
let actorClaims: Record<string, any> | null = null;
if (request.actor_token) {
actorClaims = await this.validateActorToken(
request.actor_token,
request.actor_token_type!
);
}
// Determine target audience and scopes
const targetAudience = this.resolveAudience(
request.audience,
clientId
);
const grantedScopes = this.calculateGrantedScopes(
subjectClaims,
request.scope,
targetAudience,
clientId
);
// Generate new token
const newToken = await this.generateExchangedToken(
subjectClaims,
actorClaims,
targetAudience,
grantedScopes,
request.requested_token_type
);
return {
access_token: newToken.token,
issued_token_type: newToken.type,
token_type: 'Bearer',
expires_in: newToken.expiresIn,
scope: grantedScopes.join(' ')
};
}
private async validateSubjectToken(
token: string,
tokenType: string
): Promise<Record<string, any>> {
switch (tokenType) {
case 'urn:ietf:params:oauth:token-type:access_token':
return this.validateAccessToken(token);
case 'urn:ietf:params:oauth:token-type:id_token':
return this.validateIdToken(token);
case 'urn:ietf:params:oauth:token-type:jwt':
return this.validateJWT(token);
case 'urn:ietf:params:oauth:token-type:saml2':
return this.validateSAML2Assertion(token);
default:
throw new TokenExchangeError('unsupported_token_type');
}
}
private async validateAccessToken(
token: string
): Promise<Record<string, any>> {
// Decode to find issuer
const decoded = decodeJwt(token);
const issuer = decoded.iss as string;
// Get issuer configuration
const issuerConfig = this.trustedIssuers.get(issuer);
if (!issuerConfig) {
throw new TokenExchangeError('invalid_token', 'Untrusted issuer');
}
// Verify token
const { payload } = await jwtVerify(token, issuerConfig.publicKey, {
issuer,
audience: issuerConfig.expectedAudience,
algorithms: issuerConfig.allowedAlgorithms
});
// Check if token is revoked
if (await this.isTokenRevoked(token, issuer)) {
throw new TokenExchangeError('invalid_token', 'Token revoked');
}
return payload as Record<string, any>;
}
private calculateGrantedScopes(
subjectClaims: Record<string, any>,
requestedScope: string | undefined,
targetAudience: string,
clientId: string
): string[] {
// Get subject's original scopes
const originalScopes = new Set(
(subjectClaims.scope as string)?.split(' ') || []
);
// Get audience-specific scope mapping
const audienceConfig = this.audienceMapping.get(targetAudience);
if (!audienceConfig) {
throw new TokenExchangeError('invalid_target');
}
// Calculate allowed scopes for this client
const clientAllowedScopes = new Set(
audienceConfig.clientScopeAllowlist.get(clientId) || []
);
// Parse requested scopes
const requestedScopes = requestedScope
? new Set(requestedScope.split(' '))
: originalScopes;
// Grant intersection of: original ∩ requested ∩ client-allowed
const grantedScopes: string[] = [];
for (const scope of requestedScopes) {
if (originalScopes.has(scope) && clientAllowedScopes.has(scope)) {
grantedScopes.push(scope);
}
}
if (grantedScopes.length === 0) {
throw new TokenExchangeError('invalid_scope');
}
return grantedScopes;
}
private async generateExchangedToken(
subjectClaims: Record<string, any>,
actorClaims: Record<string, any> | null,
audience: string,
scopes: string[],
requestedType?: string
): Promise<{ token: string; type: string; expiresIn: number }> {
const now = Math.floor(Date.now() / 1000);
const expiresIn = 300; // 5 minute tokens for internal services
const builder = new SignJWT({
sub: subjectClaims.sub,
scope: scopes.join(' '),
client_id: subjectClaims.client_id,
// Preserve original subject info
original_iss: subjectClaims.iss,
original_sub: subjectClaims.sub
})
.setProtectedHeader({ alg: 'RS256', typ: 'at+jwt' })
.setIssuedAt(now)
.setExpirationTime(now + expiresIn)
.setAudience(audience)
.setIssuer(process.env.GATEWAY_ISSUER!)
.setJti(randomBytes(16).toString('hex'));
// Add actor claim for delegation
if (actorClaims) {
builder.setPayload({
act: {
sub: actorClaims.sub,
client_id: actorClaims.client_id
}
});
}
const token = await builder.sign(this.signingKey);
return {
token,
type: requestedType || 'urn:ietf:params:oauth:token-type:access_token',
expiresIn
};
}
}
interface TrustedIssuer {
publicKey: CryptoKey;
expectedAudience: string;
allowedAlgorithms: string[];
}
interface AudienceConfig {
clientScopeAllowlist: Map<string, string[]>;
maxTokenLifetime: number;
}
class TokenExchangeError extends Error {
constructor(
public code: string,
message?: string
) {
super(message || code);
}
}
Internal Token Minting
// Gateway-internal token for downstream services
class InternalTokenService {
private readonly encryptionKey: Uint8Array;
private readonly hmacKey: Uint8Array;
constructor(masterKey: Uint8Array) {
// Derive separate keys for encryption and authentication
this.encryptionKey = this.deriveKey(masterKey, 'encryption');
this.hmacKey = this.deriveKey(masterKey, 'authentication');
}
async mintInternalToken(
request: InternalTokenRequest
): Promise<string> {
const payload: InternalTokenPayload = {
sub: request.subject,
aud: request.targetService,
scopes: request.scopes,
metadata: request.metadata,
iat: Date.now(),
exp: Date.now() + (request.ttlSeconds * 1000),
jti: randomBytes(16).toString('hex'),
// Request context for audit
req: {
id: request.requestId,
ip: request.clientIp,
ua: request.userAgent
}
};
// Serialize payload
const payloadBytes = new TextEncoder().encode(JSON.stringify(payload));
// Encrypt with AES-GCM
const iv = randomBytes(12);
const cipher = createCipheriv('aes-256-gcm', this.encryptionKey, iv);
const encrypted = Buffer.concat([
cipher.update(payloadBytes),
cipher.final()
]);
const authTag = cipher.getAuthTag();
// Combine: version || iv || encrypted || authTag
const token = Buffer.concat([
Buffer.from([0x01]), // Version byte
iv,
encrypted,
authTag
]);
// HMAC for integrity
const hmac = createHmac('sha256', this.hmacKey)
.update(token)
.digest();
// Final token: base64url(token || hmac)
return Buffer.concat([token, hmac]).toString('base64url');
}
async validateInternalToken(
token: string,
expectedAudience: string
): Promise<InternalTokenPayload> {
const tokenBytes = Buffer.from(token, 'base64url');
// Extract HMAC (last 32 bytes)
const hmac = tokenBytes.slice(-32);
const tokenData = tokenBytes.slice(0, -32);
// Verify HMAC
const expectedHmac = createHmac('sha256', this.hmacKey)
.update(tokenData)
.digest();
if (!timingSafeEqual(hmac, expectedHmac)) {
throw new Error('Token integrity check failed');
}
// Parse token structure
const version = tokenData[0];
if (version !== 0x01) {
throw new Error('Unsupported token version');
}
const iv = tokenData.slice(1, 13);
const authTag = tokenData.slice(-16);
const encrypted = tokenData.slice(13, -16);
// Decrypt
const decipher = createDecipheriv('aes-256-gcm', this.encryptionKey, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
const payload: InternalTokenPayload = JSON.parse(decrypted.toString());
// Validate claims
if (Date.now() > payload.exp) {
throw new Error('Token expired');
}
if (payload.aud !== expectedAudience) {
throw new Error('Audience mismatch');
}
return payload;
}
private deriveKey(masterKey: Uint8Array, purpose: string): Uint8Array {
return createHmac('sha256', masterKey)
.update(purpose)
.digest();
}
}
interface InternalTokenRequest {
subject: string;
targetService: string;
scopes: string[];
metadata: Record<string, any>;
ttlSeconds: number;
requestId: string;
clientIp: string;
userAgent: string;
}
interface InternalTokenPayload {
sub: string;
aud: string;
scopes: string[];
metadata: Record<string, any>;
iat: number;
exp: number;
jti: string;
req: {
id: string;
ip: string;
ua: string;
};
}
Scope Mapping and Policy Enforcement
Hierarchical Scope System
// Scope hierarchy and mapping system
class ScopeManager {
private scopeHierarchy: Map<string, ScopeDefinition>;
private resourceMapping: Map<string, ResourcePolicy>;
constructor(config: ScopeManagerConfig) {
this.scopeHierarchy = this.buildHierarchy(config.scopes);
this.resourceMapping = config.resources;
}
// Expand scope to include all implied scopes
expandScopes(scopes: string[]): Set<string> {
const expanded = new Set<string>();
for (const scope of scopes) {
expanded.add(scope);
// Add all scopes this implies
const definition = this.scopeHierarchy.get(scope);
if (definition?.implies) {
for (const implied of definition.implies) {
const impliedExpanded = this.expandScopes([implied]);
impliedExpanded.forEach(s => expanded.add(s));
}
}
}
return expanded;
}
// Check if scopes satisfy a policy requirement
satisfiesPolicy(
grantedScopes: string[],
resource: string,
action: string
): PolicyDecision {
const policy = this.findPolicy(resource);
if (!policy) {
return { allowed: false, reason: 'No policy found for resource' };
}
const requirement = policy.actions.get(action);
if (!requirement) {
return { allowed: false, reason: 'Action not defined in policy' };
}
const expandedGranted = this.expandScopes(grantedScopes);
// Check if any of the required scopes are satisfied
for (const requiredScope of requirement.requiredScopes) {
if (requiredScope.type === 'any') {
// Any one of the scopes is sufficient
for (const scope of requiredScope.scopes) {
if (expandedGranted.has(scope)) {
return {
allowed: true,
matchedScope: scope,
constraints: requirement.constraints
};
}
}
} else if (requiredScope.type === 'all') {
// All scopes are required
const hasAll = requiredScope.scopes.every(s => expandedGranted.has(s));
if (hasAll) {
return {
allowed: true,
matchedScope: requiredScope.scopes.join('+'),
constraints: requirement.constraints
};
}
}
}
return {
allowed: false,
reason: 'Insufficient scopes',
required: requirement.requiredScopes
};
}
private findPolicy(resource: string): ResourcePolicy | null {
// Exact match
if (this.resourceMapping.has(resource)) {
return this.resourceMapping.get(resource)!;
}
// Wildcard matching
for (const [pattern, policy] of this.resourceMapping) {
if (this.matchPattern(pattern, resource)) {
return policy;
}
}
return null;
}
private matchPattern(pattern: string, resource: string): boolean {
// Convert glob pattern to regex
const regexPattern = pattern
.replace(/\*/g, '[^/]*')
.replace(/\*\*/g, '.*');
return new RegExp(`^${regexPattern}$`).test(resource);
}
}
interface ScopeDefinition {
name: string;
description: string;
implies?: string[]; // Scopes this scope includes
constraints?: ScopeConstraint[];
}
interface ScopeConstraint {
type: 'rate_limit' | 'field_filter' | 'time_window';
config: Record<string, any>;
}
interface ResourcePolicy {
resource: string;
actions: Map<string, ActionRequirement>;
}
interface ActionRequirement {
requiredScopes: ScopeRequirement[];
constraints?: ActionConstraint[];
}
interface ScopeRequirement {
type: 'any' | 'all';
scopes: string[];
}
interface ActionConstraint {
type: 'ownership' | 'tenant' | 'field_mask';
config: Record<string, any>;
}
interface PolicyDecision {
allowed: boolean;
reason?: string;
matchedScope?: string;
constraints?: ActionConstraint[];
required?: ScopeRequirement[];
}
// Example usage in gateway
class GatewayAuthorizationMiddleware {
constructor(private scopeManager: ScopeManager) {}
async authorize(
request: GatewayRequest,
tokenClaims: TokenClaims
): Promise<AuthorizationResult> {
const resource = this.extractResource(request);
const action = this.mapMethodToAction(request.method);
// Check policy
const decision = this.scopeManager.satisfiesPolicy(
tokenClaims.scope.split(' '),
resource,
action
);
if (!decision.allowed) {
return {
allowed: false,
statusCode: 403,
error: {
code: 'insufficient_scope',
description: decision.reason,
required: decision.required
}
};
}
// Apply constraints to request
const transformedRequest = this.applyConstraints(
request,
decision.constraints || [],
tokenClaims
);
return {
allowed: true,
request: transformedRequest
};
}
private applyConstraints(
request: GatewayRequest,
constraints: ActionConstraint[],
claims: TokenClaims
): GatewayRequest {
let transformedRequest = { ...request };
for (const constraint of constraints) {
switch (constraint.type) {
case 'ownership':
// Add owner filter to query
transformedRequest = this.addOwnershipFilter(
transformedRequest,
claims.sub
);
break;
case 'tenant':
// Add tenant header for downstream
transformedRequest.headers['X-Tenant-ID'] = claims.tenant_id;
break;
case 'field_mask':
// Store field mask for response filtering
transformedRequest.headers['X-Response-Fields'] =
constraint.config.allowedFields.join(',');
break;
}
}
return transformedRequest;
}
}
Advanced Rate Limiting
Multi-Dimensional Rate Limiting
import Redis from 'ioredis';
// Rate limiter with multiple dimensions
class MultiDimensionalRateLimiter {
private redis: Redis;
private config: RateLimitConfig;
constructor(redis: Redis, config: RateLimitConfig) {
this.redis = redis;
this.config = config;
}
async checkLimit(request: RateLimitRequest): Promise<RateLimitResult> {
const results: DimensionResult[] = [];
// Check each dimension in parallel
const checks = this.config.dimensions.map(dimension =>
this.checkDimension(request, dimension)
);
const dimensionResults = await Promise.all(checks);
// Find the most restrictive dimension
let mostRestrictive: DimensionResult | null = null;
for (const result of dimensionResults) {
results.push(result);
if (!result.allowed) {
if (!mostRestrictive || result.retryAfter > mostRestrictive.retryAfter) {
mostRestrictive = result;
}
}
}
if (mostRestrictive) {
return {
allowed: false,
retryAfter: mostRestrictive.retryAfter,
limitedBy: mostRestrictive.dimension,
headers: this.buildRateLimitHeaders(results)
};
}
// Increment counters for all dimensions
await this.incrementCounters(request);
return {
allowed: true,
headers: this.buildRateLimitHeaders(results)
};
}
private async checkDimension(
request: RateLimitRequest,
dimension: RateLimitDimension
): Promise<DimensionResult> {
const key = this.buildKey(request, dimension);
const window = dimension.windowSeconds;
// Sliding window counter using Redis sorted set
const now = Date.now();
const windowStart = now - (window * 1000);
// Remove old entries and count current
const pipeline = this.redis.pipeline();
pipeline.zremrangebyscore(key, '-inf', windowStart);
pipeline.zcard(key);
pipeline.pttl(key);
const [, , [, count], [, ttl]] = await pipeline.exec() as any;
const limit = this.resolveLimit(dimension, request);
return {
dimension: dimension.name,
allowed: count < limit,
current: count,
limit,
remaining: Math.max(0, limit - count),
retryAfter: count >= limit ? Math.ceil(ttl / 1000) : 0,
resetAt: now + (ttl > 0 ? ttl : window * 1000)
};
}
private async incrementCounters(request: RateLimitRequest): Promise<void> {
const now = Date.now();
const pipeline = this.redis.pipeline();
for (const dimension of this.config.dimensions) {
const key = this.buildKey(request, dimension);
const memberId = `${now}:${Math.random().toString(36).slice(2)}`;
pipeline.zadd(key, now, memberId);
pipeline.expire(key, dimension.windowSeconds);
}
await pipeline.exec();
}
private buildKey(
request: RateLimitRequest,
dimension: RateLimitDimension
): string {
const parts = ['rl', dimension.name];
// Add dimension-specific key parts
switch (dimension.type) {
case 'client':
parts.push(`client:${request.clientId}`);
break;
case 'user':
parts.push(`user:${request.userId}`);
break;
case 'ip':
parts.push(`ip:${request.clientIp}`);
break;
case 'endpoint':
parts.push(`endpoint:${request.endpoint}`);
break;
case 'tenant':
parts.push(`tenant:${request.tenantId}`);
break;
case 'composite':
// Multiple key parts for granular limiting
parts.push(
dimension.compositeFields!
.map(f => `${f}:${request[f as keyof RateLimitRequest]}`)
.join(':')
);
break;
}
return parts.join(':');
}
private resolveLimit(
dimension: RateLimitDimension,
request: RateLimitRequest
): number {
// Check for tier-specific limits
if (dimension.tierLimits && request.tier) {
return dimension.tierLimits[request.tier] ?? dimension.defaultLimit;
}
// Check for endpoint-specific overrides
if (dimension.endpointOverrides?.[request.endpoint]) {
return dimension.endpointOverrides[request.endpoint];
}
return dimension.defaultLimit;
}
private buildRateLimitHeaders(
results: DimensionResult[]
): Record<string, string> {
// Find most restrictive for primary headers
const primary = results.reduce((min, r) =>
r.remaining < min.remaining ? r : min
);
return {
'X-RateLimit-Limit': primary.limit.toString(),
'X-RateLimit-Remaining': primary.remaining.toString(),
'X-RateLimit-Reset': Math.ceil(primary.resetAt / 1000).toString(),
// Extended headers for all dimensions
'X-RateLimit-Policy': results
.map(r => `${r.dimension}:${r.limit}`)
.join(', ')
};
}
}
interface RateLimitConfig {
dimensions: RateLimitDimension[];
}
interface RateLimitDimension {
name: string;
type: 'client' | 'user' | 'ip' | 'endpoint' | 'tenant' | 'composite';
windowSeconds: number;
defaultLimit: number;
tierLimits?: Record<string, number>;
endpointOverrides?: Record<string, number>;
compositeFields?: string[];
}
interface RateLimitRequest {
clientId: string;
userId?: string;
clientIp: string;
endpoint: string;
tenantId?: string;
tier?: string;
}
interface RateLimitResult {
allowed: boolean;
retryAfter?: number;
limitedBy?: string;
headers: Record<string, string>;
}
interface DimensionResult {
dimension: string;
allowed: boolean;
current: number;
limit: number;
remaining: number;
retryAfter: number;
resetAt: number;
}
Adaptive Rate Limiting
// Adaptive rate limiter that adjusts based on system health
class AdaptiveRateLimiter {
private baseRateLimiter: MultiDimensionalRateLimiter;
private healthMonitor: HealthMonitor;
private adaptationFactor = 1.0;
constructor(
baseLimiter: MultiDimensionalRateLimiter,
healthMonitor: HealthMonitor
) {
this.baseRateLimiter = baseLimiter;
this.healthMonitor = healthMonitor;
// Periodically adjust limits based on health
setInterval(() => this.adjustLimits(), 10000);
}
async checkLimit(request: RateLimitRequest): Promise<RateLimitResult> {
// Apply adaptation factor to request tier
const adaptedRequest = {
...request,
tier: this.getAdaptedTier(request.tier)
};
const result = await this.baseRateLimiter.checkLimit(adaptedRequest);
// Add load shedding during severe degradation
if (this.adaptationFactor < 0.5 && Math.random() > this.adaptationFactor) {
return {
allowed: false,
retryAfter: 60,
limitedBy: 'system_protection',
headers: {
...result.headers,
'X-RateLimit-Reason': 'system_protection'
}
};
}
return result;
}
private adjustLimits(): void {
const health = this.healthMonitor.getSystemHealth();
// Calculate adaptation factor based on health metrics
const factors: number[] = [];
// CPU pressure
if (health.cpuUsage > 0.9) {
factors.push(0.3);
} else if (health.cpuUsage > 0.8) {
factors.push(0.6);
} else if (health.cpuUsage > 0.7) {
factors.push(0.8);
} else {
factors.push(1.0);
}
// Memory pressure
if (health.memoryUsage > 0.9) {
factors.push(0.4);
} else if (health.memoryUsage > 0.8) {
factors.push(0.7);
} else {
factors.push(1.0);
}
// Error rate
if (health.errorRate > 0.1) {
factors.push(0.5);
} else if (health.errorRate > 0.05) {
factors.push(0.7);
} else {
factors.push(1.0);
}
// Latency
if (health.p99Latency > health.sloTarget * 2) {
factors.push(0.5);
} else if (health.p99Latency > health.sloTarget) {
factors.push(0.8);
} else {
factors.push(1.0);
}
// Use minimum factor (most conservative)
this.adaptationFactor = Math.min(...factors);
}
private getAdaptedTier(originalTier?: string): string {
// During degradation, downgrade tiers
if (this.adaptationFactor >= 0.9) {
return originalTier || 'default';
}
// Map tiers down during pressure
const tierDowngrade: Record<string, string> = {
'enterprise': 'business',
'business': 'professional',
'professional': 'standard',
'standard': 'limited',
'limited': 'minimal'
};
const tier = originalTier || 'default';
if (this.adaptationFactor < 0.5) {
// Severe degradation: downgrade twice
return tierDowngrade[tierDowngrade[tier] || tier] || tier;
}
// Moderate degradation: downgrade once
return tierDowngrade[tier] || tier;
}
}
interface HealthMonitor {
getSystemHealth(): SystemHealth;
}
interface SystemHealth {
cpuUsage: number;
memoryUsage: number;
errorRate: number;
p99Latency: number;
sloTarget: number;
}
WAF Integration
Custom WAF Rules Engine
import { z } from 'zod';
// WAF rule engine for API-specific threats
class APIWAFEngine {
private rules: WAFRule[];
private ipReputationService: IPReputationService;
private anomalyDetector: AnomalyDetector;
constructor(
rules: WAFRule[],
ipReputationService: IPReputationService,
anomalyDetector: AnomalyDetector
) {
this.rules = rules.sort((a, b) => a.priority - b.priority);
this.ipReputationService = ipReputationService;
this.anomalyDetector = anomalyDetector;
}
async inspect(request: WAFRequest): Promise<WAFDecision> {
const context: InspectionContext = {
request,
anomalyScore: 0,
matchedRules: [],
ipReputation: await this.ipReputationService.check(request.clientIp)
};
// Pre-checks
if (context.ipReputation.score < 0.2) {
return {
action: 'block',
reason: 'ip_reputation',
details: { score: context.ipReputation.score }
};
}
// Run rules
for (const rule of this.rules) {
if (!this.ruleApplies(rule, request)) continue;
const result = await this.evaluateRule(rule, context);
if (result.matched) {
context.matchedRules.push(rule.id);
context.anomalyScore += result.scoreContribution;
if (rule.action === 'block') {
return {
action: 'block',
ruleId: rule.id,
reason: rule.description
};
}
}
}
// Check cumulative anomaly score
if (context.anomalyScore >= this.config.anomalyThreshold) {
return {
action: 'block',
reason: 'anomaly_score_exceeded',
details: {
score: context.anomalyScore,
matchedRules: context.matchedRules
}
};
}
// Run ML anomaly detection
const anomalyResult = await this.anomalyDetector.analyze(request);
if (anomalyResult.isAnomaly && anomalyResult.confidence > 0.9) {
return {
action: 'challenge',
reason: 'ml_anomaly_detection',
details: anomalyResult
};
}
return { action: 'allow' };
}
private async evaluateRule(
rule: WAFRule,
context: InspectionContext
): Promise<RuleResult> {
switch (rule.type) {
case 'regex':
return this.evaluateRegexRule(rule, context);
case 'sql_injection':
return this.detectSQLInjection(rule, context);
case 'json_schema':
return this.validateJSONSchema(rule, context);
case 'parameter_tampering':
return this.detectParameterTampering(rule, context);
case 'request_smuggling':
return this.detectRequestSmuggling(rule, context);
case 'api_abuse':
return this.detectAPIAbuse(rule, context);
default:
return { matched: false, scoreContribution: 0 };
}
}
private detectSQLInjection(
rule: WAFRule,
context: InspectionContext
): RuleResult {
const patterns = [
// Union-based injection
/union\s+(all\s+)?select/i,
// Boolean-based injection
/'\s*(or|and)\s+['"]?\d+['"]?\s*=\s*['"]?\d+/i,
// Time-based injection
/sleep\s*\(\s*\d+\s*\)/i,
/benchmark\s*\(/i,
/waitfor\s+delay/i,
// Stacked queries
/;\s*(drop|delete|update|insert|exec)/i,
// Comment-based evasion
/\/\*[\s\S]*?\*\//,
// Hex encoding
/0x[0-9a-f]+/i
];
const targets = this.getInspectionTargets(context.request, rule.targets);
for (const target of targets) {
for (const pattern of patterns) {
if (pattern.test(target.value)) {
return {
matched: true,
scoreContribution: rule.anomalyScore || 5,
details: {
pattern: pattern.source,
target: target.name,
value: target.value.substring(0, 100)
}
};
}
}
}
return { matched: false, scoreContribution: 0 };
}
private detectParameterTampering(
rule: WAFRule,
context: InspectionContext
): RuleResult {
const request = context.request;
// HTTP Parameter Pollution
const duplicateParams = this.findDuplicateParams(request.queryParams);
if (duplicateParams.length > 0) {
return {
matched: true,
scoreContribution: 3,
details: { type: 'hpp', params: duplicateParams }
};
}
// Type confusion
for (const [key, value] of Object.entries(request.body || {})) {
if (this.isTypeConfusion(key, value)) {
return {
matched: true,
scoreContribution: 4,
details: { type: 'type_confusion', param: key }
};
}
}
// Mass assignment detection
const sensitiveFields = ['role', 'admin', 'is_admin', 'permissions', 'privilege'];
for (const field of sensitiveFields) {
if (request.body?.[field] !== undefined) {
return {
matched: true,
scoreContribution: 5,
details: { type: 'mass_assignment', field }
};
}
}
return { matched: false, scoreContribution: 0 };
}
private detectRequestSmuggling(
rule: WAFRule,
context: InspectionContext
): RuleResult {
const headers = context.request.headers;
// Multiple Content-Length headers
const clHeaders = Object.keys(headers).filter(
h => h.toLowerCase() === 'content-length'
);
if (clHeaders.length > 1) {
return {
matched: true,
scoreContribution: 10,
details: { type: 'multiple_content_length' }
};
}
// Conflicting Transfer-Encoding and Content-Length
if (headers['transfer-encoding'] && headers['content-length']) {
return {
matched: true,
scoreContribution: 8,
details: { type: 'te_cl_conflict' }
};
}
// Obfuscated Transfer-Encoding
const teValue = headers['transfer-encoding'];
if (teValue && /[\x00-\x1f\x7f]/.test(teValue)) {
return {
matched: true,
scoreContribution: 10,
details: { type: 'obfuscated_te' }
};
}
return { matched: false, scoreContribution: 0 };
}
private detectAPIAbuse(
rule: WAFRule,
context: InspectionContext
): RuleResult {
const request = context.request;
// Excessive field selection (GraphQL-style)
if (request.body?.query) {
const depth = this.calculateQueryDepth(request.body.query);
if (depth > rule.config?.maxQueryDepth || 10) {
return {
matched: true,
scoreContribution: 4,
details: { type: 'query_depth', depth }
};
}
}
// Batch request abuse
if (Array.isArray(request.body) && request.body.length > (rule.config?.maxBatchSize || 100)) {
return {
matched: true,
scoreContribution: 3,
details: { type: 'batch_abuse', count: request.body.length }
};
}
// Resource exhaustion via pagination
const limit = parseInt(request.queryParams.limit || '0', 10);
if (limit > (rule.config?.maxLimit || 1000)) {
return {
matched: true,
scoreContribution: 2,
details: { type: 'pagination_abuse', limit }
};
}
return { matched: false, scoreContribution: 0 };
}
}
interface WAFRequest {
method: string;
path: string;
queryParams: Record<string, string>;
headers: Record<string, string>;
body: any;
clientIp: string;
}
interface WAFRule {
id: string;
priority: number;
type: string;
description: string;
targets: string[];
action: 'block' | 'log' | 'score';
anomalyScore?: number;
config?: Record<string, any>;
conditions?: RuleCondition[];
}
interface WAFDecision {
action: 'allow' | 'block' | 'challenge';
ruleId?: string;
reason?: string;
details?: Record<string, any>;
}
interface RuleResult {
matched: boolean;
scoreContribution: number;
details?: Record<string, any>;
}
Zero Trust API Architecture
Request-Level Trust Evaluation
// Zero trust request evaluation
class ZeroTrustEvaluator {
private policyEngine: PolicyEngine;
private riskScorer: RiskScorer;
private contextEnricher: ContextEnricher;
async evaluate(request: ZeroTrustRequest): Promise<TrustDecision> {
// Enrich context with device, location, behavior data
const enrichedContext = await this.contextEnricher.enrich(request);
// Calculate risk score
const riskScore = await this.riskScorer.calculate(enrichedContext);
// Get applicable policies
const policies = await this.policyEngine.getPolicies(
enrichedContext.resource,
enrichedContext.action
);
// Evaluate each policy
const evaluations: PolicyEvaluation[] = [];
for (const policy of policies) {
const evaluation = await this.evaluatePolicy(
policy,
enrichedContext,
riskScore
);
evaluations.push(evaluation);
}
// Combine evaluations (all must pass for allow)
const denied = evaluations.find(e => e.decision === 'deny');
if (denied) {
return {
decision: 'deny',
reason: denied.reason,
policyId: denied.policyId
};
}
// Check if step-up auth required
const stepUp = evaluations.find(e => e.decision === 'step_up');
if (stepUp) {
return {
decision: 'step_up',
requirements: stepUp.requirements
};
}
return {
decision: 'allow',
constraints: this.mergeConstraints(evaluations)
};
}
private async evaluatePolicy(
policy: Policy,
context: EnrichedContext,
riskScore: RiskScore
): Promise<PolicyEvaluation> {
// Check identity requirements
if (!this.meetsIdentityRequirements(policy, context)) {
return {
policyId: policy.id,
decision: 'deny',
reason: 'identity_requirements_not_met'
};
}
// Check device requirements
if (!this.meetsDeviceRequirements(policy, context)) {
if (policy.devicePolicy.enforcement === 'required') {
return {
policyId: policy.id,
decision: 'deny',
reason: 'device_requirements_not_met'
};
}
}
// Check location requirements
if (!this.meetsLocationRequirements(policy, context)) {
return {
policyId: policy.id,
decision: 'deny',
reason: 'location_not_allowed'
};
}
// Risk-based step-up
if (riskScore.overall > policy.riskThreshold) {
if (riskScore.overall > policy.denyThreshold) {
return {
policyId: policy.id,
decision: 'deny',
reason: 'risk_score_exceeded'
};
}
return {
policyId: policy.id,
decision: 'step_up',
requirements: this.determineStepUpRequirements(riskScore)
};
}
return {
policyId: policy.id,
decision: 'allow',
constraints: policy.constraints
};
}
private meetsDeviceRequirements(
policy: Policy,
context: EnrichedContext
): boolean {
const device = context.device;
const requirements = policy.devicePolicy;
// Check if device is enrolled/managed
if (requirements.managed && !device.isManaged) {
return false;
}
// Check OS version minimums
if (requirements.minOSVersion) {
if (!this.versionGte(device.osVersion, requirements.minOSVersion)) {
return false;
}
}
// Check security posture
if (requirements.securityPosture) {
if (!device.hasScreenLock && requirements.securityPosture.screenLock) {
return false;
}
if (!device.hasEncryption && requirements.securityPosture.encryption) {
return false;
}
if (device.isJailbroken && !requirements.securityPosture.allowJailbreak) {
return false;
}
}
return true;
}
private determineStepUpRequirements(riskScore: RiskScore): StepUpRequirement[] {
const requirements: StepUpRequirement[] = [];
if (riskScore.factors.location > 0.7) {
requirements.push({
type: 'verification',
method: 'email',
reason: 'unusual_location'
});
}
if (riskScore.factors.device > 0.6) {
requirements.push({
type: 'mfa',
method: 'totp',
reason: 'new_device'
});
}
if (riskScore.factors.behavior > 0.8) {
requirements.push({
type: 'mfa',
method: 'webauthn',
reason: 'unusual_behavior'
});
}
// Always require MFA for high risk
if (riskScore.overall > 0.7 && requirements.length === 0) {
requirements.push({
type: 'mfa',
method: 'any',
reason: 'high_risk_score'
});
}
return requirements;
}
}
interface EnrichedContext {
identity: {
sub: string;
email: string;
groups: string[];
authMethods: string[];
authTime: number;
};
device: {
id: string;
isManaged: boolean;
osType: string;
osVersion: string;
hasScreenLock: boolean;
hasEncryption: boolean;
isJailbroken: boolean;
trustScore: number;
};
location: {
country: string;
region: string;
city: string;
ip: string;
isVpn: boolean;
isProxy: boolean;
riskLevel: string;
};
behavior: {
lastActive: number;
requestsLastHour: number;
failedAuthLast24h: number;
unusualPattern: boolean;
};
resource: string;
action: string;
}
interface RiskScore {
overall: number;
factors: {
location: number;
device: number;
behavior: number;
identity: number;
time: number;
};
}
interface Policy {
id: string;
resource: string;
actions: string[];
identityPolicy: {
requiredGroups?: string[];
requiredAuthMethods?: string[];
maxAuthAge?: number;
};
devicePolicy: {
managed?: boolean;
minOSVersion?: string;
enforcement: 'required' | 'preferred' | 'optional';
securityPosture?: {
screenLock?: boolean;
encryption?: boolean;
allowJailbreak?: boolean;
};
};
locationPolicy: {
allowedCountries?: string[];
blockedCountries?: string[];
allowVpn?: boolean;
allowProxy?: boolean;
};
riskThreshold: number;
denyThreshold: number;
constraints?: PolicyConstraint[];
}
interface TrustDecision {
decision: 'allow' | 'deny' | 'step_up';
reason?: string;
policyId?: string;
requirements?: StepUpRequirement[];
constraints?: PolicyConstraint[];
}
interface StepUpRequirement {
type: 'mfa' | 'verification' | 'reauthentication';
method: string;
reason: string;
}
Service Mesh Integration
mTLS and SPIFFE Identity
// Service identity and mTLS management
class ServiceMeshSecurityManager {
private spiffeWorkloadAPI: SPIFFEWorkloadAPI;
private policyStore: Map<string, ServicePolicy>;
async validateServiceIdentity(
peerCertificate: X509Certificate,
requestedService: string
): Promise<IdentityValidationResult> {
// Extract SPIFFE ID from certificate
const spiffeId = this.extractSpiffeId(peerCertificate);
if (!spiffeId) {
return {
valid: false,
reason: 'no_spiffe_id'
};
}
// Validate SPIFFE ID format
const parsed = this.parseSpiffeId(spiffeId);
if (!parsed) {
return {
valid: false,
reason: 'invalid_spiffe_id_format'
};
}
// Verify trust domain
if (parsed.trustDomain !== this.config.trustDomain) {
return {
valid: false,
reason: 'untrusted_domain',
details: { domain: parsed.trustDomain }
};
}
// Check if service is authorized to call target
const policy = this.policyStore.get(requestedService);
if (policy && !policy.allowedCallers.includes(spiffeId)) {
return {
valid: false,
reason: 'caller_not_authorized',
details: { caller: spiffeId, target: requestedService }
};
}
// Verify certificate chain
const chainValid = await this.verifyCertificateChain(peerCertificate);
if (!chainValid) {
return {
valid: false,
reason: 'invalid_certificate_chain'
};
}
return {
valid: true,
identity: {
spiffeId,
service: parsed.workload,
namespace: parsed.namespace,
trustDomain: parsed.trustDomain
}
};
}
private extractSpiffeId(cert: X509Certificate): string | null {
// SPIFFE ID is in the SAN URI extension
const sans = cert.subjectAltName;
if (!sans) return null;
const uriMatch = sans.match(/URI:spiffe:\/\/[^\s,]+/);
return uriMatch ? uriMatch[0].replace('URI:', '') : null;
}
private parseSpiffeId(spiffeId: string): ParsedSpiffeId | null {
// Format: spiffe://trust-domain/path
// Example: spiffe://example.org/ns/production/sa/api-gateway
const match = spiffeId.match(
/^spiffe:\/\/([^\/]+)\/ns\/([^\/]+)\/sa\/(.+)$/
);
if (!match) return null;
return {
trustDomain: match[1],
namespace: match[2],
workload: match[3]
};
}
// Authorization policy enforcement
async enforcePolicy(
caller: ServiceIdentity,
request: ServiceRequest
): Promise<PolicyEnforcementResult> {
const policy = this.policyStore.get(request.service);
if (!policy) {
// Default deny if no policy
return {
allowed: false,
reason: 'no_policy_defined'
};
}
// Check method-level permissions
const methodPolicy = policy.methods.get(request.method);
if (methodPolicy) {
if (!methodPolicy.allowedCallers.includes(caller.spiffeId)) {
return {
allowed: false,
reason: 'method_not_allowed_for_caller'
};
}
// Check request constraints
for (const constraint of methodPolicy.constraints) {
const satisfied = await this.checkConstraint(
constraint,
caller,
request
);
if (!satisfied) {
return {
allowed: false,
reason: 'constraint_not_satisfied',
details: { constraint: constraint.type }
};
}
}
}
return {
allowed: true,
rateLimit: policy.rateLimits.get(caller.spiffeId),
audit: policy.auditLevel
};
}
private async checkConstraint(
constraint: PolicyConstraint,
caller: ServiceIdentity,
request: ServiceRequest
): Promise<boolean> {
switch (constraint.type) {
case 'time_window':
const hour = new Date().getUTCHours();
return hour >= constraint.config.startHour &&
hour < constraint.config.endHour;
case 'request_header':
return request.headers[constraint.config.header] ===
constraint.config.value;
case 'source_namespace':
return constraint.config.namespaces.includes(caller.namespace);
case 'request_path':
return new RegExp(constraint.config.pattern).test(request.path);
default:
return true;
}
}
}
interface ServiceIdentity {
spiffeId: string;
service: string;
namespace: string;
trustDomain: string;
}
interface ParsedSpiffeId {
trustDomain: string;
namespace: string;
workload: string;
}
interface ServicePolicy {
service: string;
allowedCallers: string[];
methods: Map<string, MethodPolicy>;
rateLimits: Map<string, RateLimitPolicy>;
auditLevel: 'none' | 'minimal' | 'full';
}
interface MethodPolicy {
method: string;
allowedCallers: string[];
constraints: PolicyConstraint[];
}
Key Takeaways
- Token exchange (RFC 8693) enables secure token translation between trust domains
- Scope mapping should be hierarchical with policy-based enforcement
- Multi-dimensional rate limiting protects against various attack vectors simultaneously
- Adaptive rate limiting adjusts to system health to prevent cascading failures
- WAF rules must address API-specific threats: parameter tampering, request smuggling, API abuse
- Zero trust requires continuous validation of identity, device, location, and behavior
- Service mesh security uses SPIFFE/SPIRE for workload identity with mTLS
- Internal tokens should be encrypted and short-lived with minimal scope
What did you think?