Back to Blog

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

  1. Token exchange (RFC 8693) enables secure token translation between trust domains
  2. Scope mapping should be hierarchical with policy-based enforcement
  3. Multi-dimensional rate limiting protects against various attack vectors simultaneously
  4. Adaptive rate limiting adjusts to system health to prevent cascading failures
  5. WAF rules must address API-specific threats: parameter tampering, request smuggling, API abuse
  6. Zero trust requires continuous validation of identity, device, location, and behavior
  7. Service mesh security uses SPIFFE/SPIRE for workload identity with mTLS
  8. Internal tokens should be encrypted and short-lived with minimal scope

What did you think?

© 2026 Vidhya Sagar Thakur. All rights reserved.