Skip to main content

Command Palette

Search for a command to run...

API Gateway Authentication: Zero Trust Security

JWT validation and rate limiting at the edge with Kong and Tyk

Published
8 min read
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Content Role: pillar

API Gateway Authentication: Zero Trust Security

JWT validation and rate limiting at the edge with Kong and Tyk

Modern distributed systems face a critical challenge: authenticating requests across dozens or hundreds of microservices without creating security gaps or performance bottlenecks. Traditional perimeter-based security models fail in cloud-native architectures where services communicate across network boundaries, containers scale dynamically, and attack surfaces multiply exponentially.

API gateway authentication solves this problem by centralizing security enforcement at the edge. Instead of implementing authentication logic in every microservice, you validate credentials once at the gateway layer, then propagate verified identity claims downstream. This approach aligns with zero trust principles: never trust, always verify, and enforce least-privilege access at every entry point.

The Problem with Distributed Authentication

When authentication logic lives inside individual services, several problems emerge:

Inconsistent security policies across teams and services create vulnerabilities. One team might implement robust JWT validation while another accepts expired tokens or skips signature verification entirely.

Performance degradation occurs when every service independently validates tokens, queries user databases, or calls external identity providers. A single request traversing five microservices triggers five redundant authentication operations.

Operational complexity multiplies as you manage authentication libraries, certificate rotation, and security patches across dozens of codebases in different languages and frameworks.

Audit and compliance gaps appear because authentication events scatter across service logs without centralized visibility into who accessed what resources and when.

API gateway authentication addresses these issues by establishing a single enforcement point where all requests undergo consistent security checks before reaching backend services.

Zero Trust Architecture at the Gateway Layer

Zero trust security assumes breach: no user, device, or service is trusted by default, regardless of network location. For API gateways, this translates to specific implementation requirements:

Verify every request explicitly using strong authentication mechanisms like JWT tokens with cryptographic signatures, not just API keys or IP allowlists.

Apply least-privilege access by validating not just identity but also authorization claims, scopes, and permissions before routing requests.

Assume breach and minimize blast radius through rate limiting, request validation, and circuit breakers that prevent compromised credentials from causing system-wide damage.

Inspect and log all traffic to detect anomalies, support forensic analysis, and maintain compliance audit trails.

Implementing JWT Validation at the Edge

JWT (JSON Web Token) validation at the gateway provides stateless authentication that scales horizontally without session storage. Here's a production-ready implementation using TypeScript:

import * as jwt from 'jsonwebtoken';
import * as jwksClient from 'jwks-rsa';

interface JWTPayload {
  sub: string;
  email: string;
  scope: string[];
  exp: number;
  iat: number;
}

class GatewayAuthenticator {
  private jwksClient: jwksClient.JwksClient;
  private issuer: string;
  private audience: string;

  constructor(jwksUri: string, issuer: string, audience: string) {
    this.jwksClient = jwksClient({
      jwksUri,
      cache: true,
      cacheMaxAge: 600000, // 10 minutes
      rateLimit: true,
      jwksRequestsPerMinute: 10
    });
    this.issuer = issuer;
    this.audience = audience;
  }

  private async getSigningKey(kid: string): Promise<string> {
    const key = await this.jwksClient.getSigningKey(kid);
    return key.getPublicKey();
  }

  async validateToken(token: string): Promise<JWTPayload> {
    // Decode header to get key ID
    const decoded = jwt.decode(token, { complete: true });

    if (!decoded || typeof decoded === 'string') {
      throw new Error('Invalid token format');
    }

    const { kid } = decoded.header;
    if (!kid) {
      throw new Error('Token missing key ID');
    }

    // Fetch public key from JWKS endpoint
    const signingKey = await this.getSigningKey(kid);

    // Verify signature and claims
    const payload = jwt.verify(token, signingKey, {
      issuer: this.issuer,
      audience: this.audience,
      algorithms: ['RS256', 'RS384', 'RS512']
    }) as JWTPayload;

    // Additional validation
    if (!payload.scope || payload.scope.length === 0) {
      throw new Error('Token missing required scopes');
    }

    return payload;
  }

  async enrichRequest(
    token: string,
    headers: Record<string, string>
  ): Promise<Record<string, string>> {
    const payload = await this.validateToken(token);

    // Forward verified claims to backend services
    return {
      ...headers,
      'X-User-ID': payload.sub,
      'X-User-Email': payload.email,
      'X-User-Scopes': payload.scope.join(','),
      'X-Auth-Method': 'jwt'
    };
  }
}

This implementation handles key rotation automatically through JWKS (JSON Web Key Set) endpoints, caches public keys to minimize latency, and enriches requests with verified identity claims that downstream services can trust without re-validation.

Rate Limiting for Zero Trust Defense

Rate limiting prevents credential stuffing, brute force attacks, and API abuse even when attackers possess valid credentials. Implement multi-tier rate limiting:

interface RateLimitConfig {
  windowMs: number;
  maxRequests: number;
  keyGenerator: (req: Request) => string;
}

class AdaptiveRateLimiter {
  private limits: Map<string, RateLimitConfig[]>;
  private counters: Map<string, Map<string, number>>;

  constructor() {
    this.limits = new Map();
    this.counters = new Map();
  }

  addLimit(tier: string, config: RateLimitConfig): void {
    if (!this.limits.has(tier)) {
      this.limits.set(tier, []);
    }
    this.limits.get(tier)!.push(config);
  }

  async checkLimit(tier: string, req: Request): Promise<boolean> {
    const configs = this.limits.get(tier);
    if (!configs) return true;

    for (const config of configs) {
      const key = `${tier}:${config.keyGenerator(req)}`;
      const windowKey = `${key}:${Math.floor(Date.now() / config.windowMs)}`;

      if (!this.counters.has(windowKey)) {
        this.counters.set(windowKey, new Map());
      }

      const counter = this.counters.get(windowKey)!;
      const count = (counter.get(key) || 0) + 1;

      if (count > config.maxRequests) {
        return false;
      }

      counter.set(key, count);
    }

    return true;
  }
}

// Configure multi-tier limits
const limiter = new AdaptiveRateLimiter();

// Global IP-based limit
limiter.addLimit('global', {
  windowMs: 60000,
  maxRequests: 1000,
  keyGenerator: (req) => req.ip
});

// Per-user authenticated limit
limiter.addLimit('authenticated', {
  windowMs: 60000,
  maxRequests: 100,
  keyGenerator: (req) => req.headers['x-user-id'] || req.ip
});

// Per-endpoint sensitive operations
limiter.addLimit('sensitive', {
  windowMs: 300000,
  maxRequests: 10,
  keyGenerator: (req) => `${req.headers['x-user-id']}:${req.path}`
});

Kong Gateway Configuration

Kong provides declarative configuration for authentication and rate limiting:

services:
  - name: user-service
    url: http://user-service:8080
    routes:
      - name: user-routes
        paths:
          - /api/users
    plugins:
      - name: jwt
        config:
          uri_param_names:
            - jwt
          cookie_names:
            - jwt
          key_claim_name: kid
          secret_is_base64: false
          claims_to_verify:
            - exp
            - nbf
      - name: rate-limiting
        config:
          minute: 100
          hour: 1000
          policy: redis
          redis_host: redis.default.svc.cluster.local
          redis_port: 6379
          fault_tolerant: true
      - name: request-transformer
        config:
          add:
            headers:
              - X-User-ID:$(jwt.sub)
              - X-User-Email:$(jwt.email)

Tyk Gateway Configuration

Tyk uses JSON-based API definitions:

{
  "name": "User Service API",
  "api_id": "user-service",
  "org_id": "default",
  "use_keyless": false,
  "use_standard_auth": false,
  "use_jwt": true,
  "jwt_signing_method": "rsa",
  "jwt_source": "header:Authorization",
  "jwt_identity_base_field": "sub",
  "jwt_policy_field_name": "pol",
  "jwt_default_policies": ["default-policy"],
  "enable_jwt": true,
  "proxy": {
    "listen_path": "/api/users/",
    "target_url": "http://user-service:8080",
    "strip_listen_path": true
  },
  "version_data": {
    "default_version": "v1",
    "versions": {
      "v1": {
        "name": "v1",
        "use_extended_paths": true,
        "extended_paths": {
          "rate_limit": [
            {
              "path": "/",
              "method": "GET",
              "rate": 100,
              "per": 60
            }
          ],
          "transform_headers": [
            {
              "path": "/",
              "method": "",
              "add_headers": {
                "X-User-ID": "$tyk_context.jwt_claims_sub",
                "X-User-Email": "$tyk_context.jwt_claims_email"
              }
            }
          ]
        }
      }
    }
  }
}

Common Pitfalls

Trusting the Authorization header blindly: Always validate JWT signatures against trusted public keys. Attackers can craft tokens with valid structure but invalid signatures.

Ignoring token expiration: Implement strict expiration checking and reject tokens with exp claims in the past. Never extend token lifetime at the gateway.

Caching public keys indefinitely: Key rotation is critical for security. Cache JWKS responses for 10-15 minutes maximum and implement fallback refresh on validation failures.

Rate limiting by IP only: In cloud environments, many users share IP addresses through NAT or proxies. Combine IP-based limits with authenticated user limits.

Logging sensitive data: Never log complete JWT tokens or Authorization headers. Log only non-sensitive claims like subject ID and timestamp.

Synchronous external calls: Validating tokens against external identity providers synchronously adds latency. Use JWT with local signature verification instead.

Missing circuit breakers: When JWKS endpoints or Redis rate limit stores fail, implement graceful degradation rather than blocking all traffic.

Best Practices Checklist

  • [ ] Validate JWT signatures using RS256 or stronger algorithms
  • [ ] Verify issuer, audience, and expiration claims
  • [ ] Cache JWKS public keys with appropriate TTL
  • [ ] Implement multi-tier rate limiting (IP, user, endpoint)
  • [ ] Use Redis or similar for distributed rate limit counters
  • [ ] Forward verified identity claims to backend services
  • [ ] Log authentication events with correlation IDs
  • [ ] Rotate signing keys regularly (quarterly minimum)
  • [ ] Monitor authentication failure rates and anomalies
  • [ ] Implement circuit breakers for external dependencies
  • [ ] Test token validation with expired and malformed tokens
  • [ ] Document required scopes for each endpoint
  • [ ] Use mutual TLS for gateway-to-service communication
  • [ ] Implement request size limits to prevent DoS
  • [ ] Configure appropriate timeout values for all operations

Frequently Asked Questions

Q: Should I use JWT or OAuth2 opaque tokens at the gateway?

JWT tokens enable stateless validation at the gateway without calling external services, reducing latency and improving scalability. Use JWT when performance matters and token size isn't a concern. Opaque tokens require gateway-to-authorization-server calls for validation but support immediate revocation and smaller token sizes.

Q: How do I handle token refresh at the gateway layer?

Don't implement token refresh at the gateway. Clients should refresh tokens with the authorization server before expiration. The gateway only validates tokens, never issues or refreshes them. This separation of concerns maintains security boundaries.

Q: What rate limiting strategy works best for APIs with varying traffic patterns?

Implement adaptive rate limiting with multiple tiers: global limits prevent infrastructure overload, per-user limits ensure fair usage, and per-endpoint limits protect sensitive operations. Use sliding window algorithms rather than fixed windows to prevent burst traffic at window boundaries.

Q: How do I test API gateway authentication in development environments?

Generate test JWT tokens using the same signing algorithm as production but with development-specific keys. Tools like jwt.io help create tokens manually. For automated testing, use libraries like jsonwebtoken to generate tokens programmatically with controlled claims and expiration times.

Q: Should the gateway validate authorization (permissions) or just authentication (identity)?

Validate both when possible. Check JWT scope claims at the gateway to enforce coarse-grained authorization (e.g., "read:users" scope for GET /users). Delegate fine-grained authorization (e.g., "can user A access user B's data") to backend services with full business context.

Q: How do I handle authentication for service-to-service communication behind the gateway?

Use mutual TLS (mTLS) for service-to-service authentication within your cluster. The gateway validates external client credentials, then backend services validate the gateway's certificate. This creates defense in depth even if the gateway is compromised.

Q: What's the performance impact of JWT validation at the gateway?

JWT signature verification using RS256 takes 1-3ms per request with cached public keys. JWKS key fetching adds 50-200ms but occurs only during cache misses (every 10-15 minutes). For high-throughput APIs, this overhead is negligible compared to backend processing time.