Cybersecurity

API Gateway Security: Authentication, Rate Limiting, and WAF Configuration

Secure your APIs at the gateway level with authentication, rate limiting, and WAF protection. Covers Kong, AWS API Gateway, and cloud-native solutions.

By Inventive HQ Team

An API gateway secures your APIs by forcing every request through a single enforcement point that authenticates the caller, rate-limits abusive traffic, and inspects request content for attacks before anything reaches your backend services. In practice that means four ordered controls at the edge: TLS termination and WAF inspection on ingress, identity verification (API keys, JWT, OAuth, or mTLS), authorization against scopes and roles, and rate limiting tiered by client and endpoint. Get that order right and the backend never sees an unauthenticated or unthrottled request; get it wrong and the gateway becomes an expensive passthrough.

That's the summary an AI Overview gives you. What it can't show is the order the checks fire in and what each layer actually blocks — which is where most misconfigurations live (a WAF that runs after auth still lets attackers probe your login endpoint; rate limiting keyed on IP alone collapses behind a corporate NAT). The animated request-flow diagram below traces a single request through every gate, and the rest of this guide gives you copy-pasteable, security-hardened configs for Kong, AWS API Gateway, and Nginx.

How a Request Flows Through the Gateway

API gateway request-flow security layers A request travels from the internet through CDN/DDoS protection, then through the gateway's ingress, authentication, authorization, and rate-limiting gates before reaching backend services. A moving dot shows the request advancing through each gate.

Ingress request path — each gate can reject before the next runs

Internet client / attacker CDN / DDoS volumetric filter Cloudflare / Shield API GATEWAY 1. Ingress TLS termination WAF rules size / IP limits 2. AuthN API key / JWT OAuth / OIDC mTLS 3. AuthZ scope check role / RBAC resource policy 4. Throttle per-client per-endpoint sliding window

Any gate fails → 401 / 403 / 429, request never reaches backend

Egress: audit log · error sanitization · security headers · response filtering every allowed request is logged with identity, path, status, and latency Backend services
Controls fire in order — ingress/WAF, then authentication, then authorization, then rate limiting. A request rejected at any gate never touches the backend.

Gateway Comparison

FeatureKongAWS API GatewayAzure APIMNginx Plus
DeploymentSelf-hosted/CloudManagedManagedSelf-hosted
Auth MethodsAll (plugins)IAM, Cognito, LambdaAAD, OAuth, KeysBasic, JWT
Rate LimitingRedis-backedBuilt-inBuilt-inZone-based
WAFPlugin/externalAWS WAFAzure WAFModSecurity
Cost ModelOpen source + EnterprisePer requestPer call + capacityLicense
Best ForMulti-cloud, flexibilityAWS-nativeAzure-nativePerformance
Choose it whenYou run on-prem/hybrid or need custom plugins and full control of the data planeYour compute is already Lambda/ECS and you want zero infra to manageYour stack is Azure and you need built-in developer portal + subscriptionsYou already run Nginx and want a lightweight gateway without new tooling

Kong Gateway Configuration

Basic Security Setup

# kong.yml - Declarative configuration
_format_version: "3.0"

services:
  - name: user-service
    url: http://user-service:8080
    routes:
      - name: user-routes
        paths:
          - /api/v1/users
        strip_path: false

plugins:
  # Global rate limiting
  - name: rate-limiting
    config:
      minute: 100
      hour: 1000
      policy: redis
      redis_host: redis
      redis_port: 6379

  # JWT authentication
  - name: jwt
    config:
      key_claim_name: kid
      claims_to_verify:
        - exp

  # Request size limiting
  - name: request-size-limiting
    config:
      allowed_payload_size: 10  # MB

  # IP restriction
  - name: ip-restriction
    config:
      allow:
        - 10.0.0.0/8
        - 192.168.0.0/16

JWT Validation Plugin

# Per-service JWT configuration
plugins:
  - name: jwt
    service: user-service
    config:
      key_claim_name: kid
      claims_to_verify:
        - exp
        - nbf
      maximum_expiration: 3600  # 1 hour max token lifetime

consumers:
  - username: mobile-app
    jwt_secrets:
      - key: mobile-app-key
        algorithm: RS256
        rsa_public_key: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
          -----END PUBLIC KEY-----

Advanced Rate Limiting

plugins:
  # Tiered rate limiting based on consumer
  - name: rate-limiting-advanced
    service: user-service
    config:
      limit:
        - second: 10
          minute: 100
        - second: 50    # Higher tier
          minute: 500
      identifier: consumer
      window_type: sliding
      sync_rate: 10

      # Redis cluster for distributed limiting
      strategy: redis
      redis:
        host: redis-cluster
        port: 6379
        cluster_addresses:
          - redis-1:6379
          - redis-2:6379
          - redis-3:6379

Request Transformation & Validation

plugins:
  # Validate request against OpenAPI schema
  - name: request-validator
    service: user-service
    config:
      body_schema: |
        {
          "type": "object",
          "properties": {
            "email": {"type": "string", "format": "email"},
            "name": {"type": "string", "minLength": 1, "maxLength": 100}
          },
          "required": ["email", "name"]
        }

  # Add security headers to responses
  - name: response-transformer
    config:
      add:
        headers:
          - X-Content-Type-Options:nosniff
          - X-Frame-Options:DENY
          - Strict-Transport-Security:max-age=31536000

AWS API Gateway Configuration

REST API with Lambda Authorizer

# serverless.yml (Serverless Framework)
service: secure-api

provider:
  name: aws
  runtime: nodejs18.x
  stage: ${opt:stage, 'dev'}

  # API Gateway settings
  apiGateway:
    shouldStartNameWithService: true
    minimumCompressionSize: 1024

functions:
  # Custom authorizer
  authorizer:
    handler: src/authorizer.handler

  # Protected endpoint
  getUsers:
    handler: src/users.get
    events:
      - http:
          path: /users
          method: get
          authorizer:
            name: authorizer
            resultTtlInSeconds: 300
            identitySource: method.request.header.Authorization
            type: token

resources:
  Resources:
    # WAF WebACL
    ApiWafAcl:
      Type: AWS::WAFv2::WebACL
      Properties:
        Name: api-waf-acl
        Scope: REGIONAL
        DefaultAction:
          Allow: {}
        Rules:
          - Name: AWSManagedRulesCommonRuleSet
            Priority: 1
            Statement:
              ManagedRuleGroupStatement:
                VendorName: AWS
                Name: AWSManagedRulesCommonRuleSet
            OverrideAction:
              None: {}
            VisibilityConfig:
              SampledRequestsEnabled: true
              CloudWatchMetricsEnabled: true
              MetricName: CommonRuleSetMetric

          - Name: RateLimitRule
            Priority: 2
            Statement:
              RateBasedStatement:
                Limit: 2000
                AggregateKeyType: IP
            Action:
              Block: {}
            VisibilityConfig:
              SampledRequestsEnabled: true
              CloudWatchMetricsEnabled: true
              MetricName: RateLimitMetric
Advertisement

Lambda Authorizer Implementation

// src/authorizer.js
const jwt = require('jsonwebtoken');

const JWKS_URI = process.env.JWKS_URI;
const ISSUER = process.env.JWT_ISSUER;
const AUDIENCE = process.env.JWT_AUDIENCE;

// Cache JWKS
let jwksCache = null;
let jwksCacheTime = 0;
const CACHE_TTL = 3600000; // 1 hour

async function getSigningKey(kid) {
  if (!jwksCache || Date.now() - jwksCacheTime > CACHE_TTL) {
    const response = await fetch(JWKS_URI);
    jwksCache = await response.json();
    jwksCacheTime = Date.now();
  }

  const key = jwksCache.keys.find(k => k.kid === kid);
  if (!key) throw new Error('Key not found');

  return jwt.createPublicKey({ key, format: 'jwk' });
}

exports.handler = async (event) => {
  try {
    const token = event.authorizationToken.replace('Bearer ', '');

    // Decode header to get key ID
    const decoded = jwt.decode(token, { complete: true });
    if (!decoded) {
      return generatePolicy('user', 'Deny', event.methodArn);
    }

    // Get signing key
    const signingKey = await getSigningKey(decoded.header.kid);

    // Verify token
    const verified = jwt.verify(token, signingKey, {
      issuer: ISSUER,
      audience: AUDIENCE,
      algorithms: ['RS256']
    });

    // Generate allow policy with context
    const policy = generatePolicy(verified.sub, 'Allow', event.methodArn);
    policy.context = {
      userId: verified.sub,
      email: verified.email,
      scopes: verified.scope || ''
    };

    return policy;

  } catch (error) {
    console.error('Authorization failed:', error.message);
    return generatePolicy('user', 'Deny', event.methodArn);
  }
};

function generatePolicy(principalId, effect, resource) {
  return {
    principalId,
    policyDocument: {
      Version: '2012-10-17',
      Statement: [{
        Action: 'execute-api:Invoke',
        Effect: effect,
        Resource: resource
      }]
    }
  };
}

Usage Plans and API Keys

# CloudFormation / SAM template
Resources:
  ApiKey:
    Type: AWS::ApiGateway::ApiKey
    Properties:
      Name: partner-api-key
      Enabled: true

  UsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    Properties:
      UsagePlanName: partner-plan
      Throttle:
        BurstLimit: 100
        RateLimit: 50
      Quota:
        Limit: 10000
        Period: MONTH
      ApiStages:
        - ApiId: !Ref ApiGateway
          Stage: prod

  UsagePlanKey:
    Type: AWS::ApiGateway::UsagePlanKey
    Properties:
      KeyId: !Ref ApiKey
      KeyType: API_KEY
      UsagePlanId: !Ref UsagePlan

Nginx as API Gateway

Security Configuration

# /etc/nginx/conf.d/api-gateway.conf

# Rate limiting zones
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $http_x_api_key zone=api_key_limit:10m rate=100r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

# Upstream backends
upstream user_service {
    zone user_service 64k;
    server user-service-1:8080 weight=5;
    server user-service-2:8080 weight=5;
    keepalive 32;
}

# API key validation map
map $http_x_api_key $api_client_name {
    default "";
    "key_abc123" "mobile-app";
    "key_def456" "web-app";
    "key_ghi789" "partner-app";
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    # TLS configuration
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

    # Security headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Strict-Transport-Security "max-age=31536000" always;

    # Request size limits
    client_max_body_size 10m;
    client_body_buffer_size 128k;

    # Timeouts
    proxy_connect_timeout 10s;
    proxy_read_timeout 30s;
    proxy_send_timeout 30s;

    # API key validation
    location /api/ {
        # Require API key
        if ($api_client_name = "") {
            return 401 '{"error": "Missing or invalid API key"}';
        }

        # Rate limiting
        limit_req zone=api_key_limit burst=20 nodelay;
        limit_conn conn_limit 10;

        # JWT validation (using auth_request)
        auth_request /auth;
        auth_request_set $user_id $upstream_http_x_user_id;

        # Forward to backend
        proxy_pass http://user_service;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-API-Client $api_client_name;
        proxy_set_header X-User-ID $user_id;

        # Hide internal headers
        proxy_hide_header X-Powered-By;
    }

    # Internal auth endpoint
    location = /auth {
        internal;
        proxy_pass http://auth-service:8080/validate;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header Authorization $http_authorization;
    }

    # Block common attacks
    location ~* (\.\.\/|\.\.\\|%2e%2e%2f|%2e%2e\/|\.%2e\/|%2e\.\/|\.\.\/) {
        return 403;
    }
}

WAF Rules Configuration

OWASP ModSecurity Core Rule Set

# /etc/nginx/modsecurity/main.conf
Include /etc/nginx/modsecurity/modsecurity.conf
Include /etc/nginx/modsecurity/crs/crs-setup.conf
Include /etc/nginx/modsecurity/crs/rules/*.conf

# Custom API rules
SecRule REQUEST_URI "@beginsWith /api/" \
    "id:10001,\
    phase:1,\
    pass,\
    nolog,\
    setvar:tx.allowed_methods=GET POST PUT DELETE PATCH"

# Block SQL injection in JSON
SecRule REQUEST_BODY "@rx (?i)(\b(select|insert|update|delete|drop|union|exec)\b)" \
    "id:10002,\
    phase:2,\
    deny,\
    status:403,\
    log,\
    msg:'SQL Injection attempt in request body'"

# Limit JSON depth
SecRule REQUEST_HEADERS:Content-Type "@contains application/json" \
    "id:10003,\
    phase:1,\
    pass,\
    setvar:tx.max_json_depth=10"

AWS WAF Rules

{
  "Name": "APISecurityRules",
  "Rules": [
    {
      "Name": "SQLInjectionRule",
      "Priority": 1,
      "Statement": {
        "SqliMatchStatement": {
          "FieldToMatch": {
            "Body": {}
          },
          "TextTransformations": [
            {"Priority": 0, "Type": "URL_DECODE"},
            {"Priority": 1, "Type": "HTML_ENTITY_DECODE"}
          ]
        }
      },
      "Action": {"Block": {}},
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "SQLInjection"
      }
    },
    {
      "Name": "XSSRule",
      "Priority": 2,
      "Statement": {
        "XssMatchStatement": {
          "FieldToMatch": {
            "Body": {}
          },
          "TextTransformations": [
            {"Priority": 0, "Type": "URL_DECODE"},
            {"Priority": 1, "Type": "HTML_ENTITY_DECODE"}
          ]
        }
      },
      "Action": {"Block": {}},
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "XSS"
      }
    },
    {
      "Name": "RateLimitRule",
      "Priority": 3,
      "Statement": {
        "RateBasedStatement": {
          "Limit": 2000,
          "AggregateKeyType": "IP"
        }
      },
      "Action": {"Block": {}},
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "RateLimit"
      }
    }
  ]
}

Monitoring and Alerting

Key Metrics to Monitor

# Prometheus alerting rules
groups:
  - name: api-gateway-alerts
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(api_requests_total{status=~"5.."}[5m]))
          / sum(rate(api_requests_total[5m])) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "API error rate above 5%"

      - alert: HighLatency
        expr: |
          histogram_quantile(0.95,
            sum(rate(api_request_duration_seconds_bucket[5m])) by (le)
          ) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "95th percentile latency above 2 seconds"

      - alert: RateLimitExceeded
        expr: |
          sum(rate(api_rate_limit_exceeded_total[5m])) > 100
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "High rate of rate-limited requests"

      - alert: AuthenticationFailures
        expr: |
          sum(rate(api_auth_failures_total[5m])) > 50
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High authentication failure rate"

Common Gateway Misconfigurations (Symptom → Cause → Fix)

These are the failures we see most often in API gateway audits — the ones an AI summary of "best practices" won't warn you about because they only surface under real traffic.

SymptomRoot causeFix
Legitimate users behind one office get 429s all dayRate limit keyed on client IP only; corporate NAT makes hundreds of users look like oneKey limits on authenticated identity (consumer/API key/sub claim), fall back to IP only for anonymous routes
Attackers still brute-force /login despite a WAFWAF/auth ordered so auth runs first and unauthenticated requests skip content inspection, or the login route is exemptedRun WAF + IP throttling on ingress before authentication; add a stricter per-route limit on auth endpoints
Valid JWTs from an old signing key are acceptedGateway pins one static key instead of rotating via JWKS, or doesn't verify iss/audFetch keys from the JWKS endpoint by kid, and verify exp, nbf, iss, and aud — not just the signature
Backend reachable directly, bypassing the gatewayNo network segmentation; services hold public IPs or open security groupsPut backends on a private network; allow ingress only from the gateway's security group / service mesh
Internal error messages and stack traces leak to clientsNo egress/response filtering; backend 500s pass through verbatimSanitize error responses at the gateway, return generic bodies, and log the real detail server-side
Admin API (Kong :8001, gateway config) exposedAdmin listener bound to 0.0.0.0 without authBind admin API to localhost/internal network, require mTLS + RBAC, never expose it publicly
Rate limits reset when a gateway node restartsCounters held in local memory instead of shared stateStore rate-limit state in Redis (or a distributed store) so limits are consistent across nodes

Best Practices

  1. Defense in depth - Layer CDN, WAF, and gateway security
  2. Fail closed - Reject requests when security checks fail
  3. Validate everything - Schema validation, size limits, content types
  4. Log comprehensively - All requests with identity and timing
  5. Rate limit at multiple levels - Global, per-API, per-client
  6. Use short token lifetimes - Validate JWTs, check expiration
  7. Secure the gateway itself - Restrict admin access, audit changes
  8. Monitor actively - Alert on anomalies, not just errors
  9. Keep updated - Patch gateway software and WAF rules
  10. Test regularly - Penetration testing, chaos engineering

Next Steps

Frequently Asked Questions

What is an API gateway and why do I need one for security?

An API gateway is a reverse proxy that sits between clients and your backend services, handling cross-cutting concerns like authentication, rate limiting, logging, and request transformation. For security, it provides a single enforcement point for access control, protects backend services from direct exposure, enables consistent security policies across all APIs, and simplifies certificate management and TLS termination.

What security features should an API gateway provide?

Essential security features include authentication (API keys, OAuth, JWT validation), authorization (scope/role checking), rate limiting and throttling, request validation (schema, size limits), IP allowlisting/blocklisting, WAF integration (SQL injection, XSS protection), TLS termination with modern cipher suites, request/response logging for audit trails, and DDoS protection.

How do I implement JWT validation at the API gateway?

Configure the gateway to validate JWTs before forwarding requests. This includes verifying the signature (using the correct algorithm and key), checking expiration (exp claim), validating issuer (iss) and audience (aud) claims, and optionally extracting claims for downstream services. Most gateways (Kong, AWS API Gateway, Azure APIM) have built-in JWT plugins that handle this automatically.

What is the difference between Kong and AWS API Gateway?

Kong is an open-source, self-hosted gateway built on Nginx/OpenResty, offering maximum flexibility and plugin extensibility—ideal for multi-cloud or on-premise deployments. AWS API Gateway is a fully managed service tightly integrated with AWS (Lambda, IAM, Cognito), best for AWS-native architectures. Kong requires infrastructure management but offers more customization; AWS API Gateway is serverless but AWS-locked.

How should I configure rate limiting at the API gateway?

Implement multiple rate limiting tiers: global limits (protect infrastructure), per-API limits (based on resource cost), per-client limits (based on plan/tier), and per-endpoint limits (expensive operations). Use sliding window algorithms for accuracy. Return 429 status with Retry-After header. Consider allowing bursts for legitimate traffic spikes. Store rate limit state in Redis for distributed deployments.

How do I protect my API gateway from DDoS attacks?

Layer your defenses: use a CDN/DDoS protection service (Cloudflare, AWS Shield) in front of the gateway, configure connection limits and timeouts, implement progressive rate limiting that tightens under load, use geographic restrictions if appropriate, enable request size limits, and set up alerting for traffic anomalies. The gateway should fail closed—reject requests when overwhelmed rather than passing them through.

Should I use a WAF with my API gateway?

Yes, a WAF (Web Application Firewall) adds defense-in-depth by inspecting request content for attacks. Configure rules for SQL injection, XSS, command injection, and path traversal. Use managed rule sets (OWASP Core Rule Set) as a baseline. Be careful with false positives—test thoroughly and tune rules for your API's specific patterns. Many gateways integrate with WAFs (AWS WAF, Cloudflare, ModSecurity).

How do I handle API versioning at the gateway level?

The gateway can route requests to different backend versions based on URL path (/v1/, /v2/), headers (Accept-Version), or query parameters. This decouples versioning from backend deployment. The gateway can also transform requests/responses between versions, allowing gradual migration. Set up routing rules to direct traffic and configure deprecation warnings via response headers for old versions.

What logging and monitoring should I enable on my API gateway?

Log all requests with: timestamp, client IP, authentication identity, request method/path, response status, latency, and request ID for tracing. Mask sensitive data (tokens, PII) in logs. Send logs to a centralized system (ELK, Splunk, CloudWatch). Set up alerts for error rate spikes, latency increases, authentication failures, and rate limit hits. Use distributed tracing (Jaeger, X-Ray) for debugging.

How do I secure the API gateway itself?

Secure the gateway infrastructure: restrict admin API access to internal networks with strong authentication (mTLS, RBAC), keep gateway software updated, use infrastructure-as-code for configuration (prevents drift), enable audit logging for config changes, run in a hardened container/VM with minimal privileges, and implement network segmentation so the gateway is the only entry point to backend services.

API securityAPI gatewayrate limitingWAFKongAWS API Gateway
Advertisement