Home/Blog/Cybersecurity/API Gateway Security: Authentication, Rate Limiting, and WAF Configuration
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
API Gateway Security: Authentication, Rate Limiting, and WAF Configuration

An API gateway provides a centralized security enforcement point for all your APIs, handling authentication, rate limiting, and threat protection before requests reach your backend services. This guide covers security configuration for popular gateway solutions.

API Gateway Security Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                        API GATEWAY SECURITY LAYERS                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Internet ──► CDN/DDoS ──► API Gateway ──► Backend Services                 │
│                              │                                               │
│              ┌───────────────┴───────────────┐                              │
│              │                               │                              │
│              ▼                               ▼                              │
│    ┌──────────────────┐           ┌──────────────────┐                     │
│    │  INGRESS SECURITY │           │ EGRESS SECURITY  │                     │
│    ├──────────────────┤           ├──────────────────┤                     │
│    │ • TLS Termination │           │ • Response filtering│                  │
│    │ • IP Allowlisting │           │ • Header injection  │                  │
│    │ • Rate Limiting   │           │ • Error sanitization│                  │
│    │ • Request Size    │           │ • Audit Logging     │                  │
│    │ • WAF Rules       │           │ • Encryption        │                  │
│    └──────────────────┘           └──────────────────┘                     │
│              │                               │                              │
│              ▼                               ▼                              │
│    ┌──────────────────┐           ┌──────────────────┐                     │
│    │  AUTHENTICATION  │           │  AUTHORIZATION    │                     │
│    ├──────────────────┤           ├──────────────────┤                     │
│    │ • API Keys        │           │ • Scope validation │                   │
│    │ • JWT Validation  │           │ • Role checking    │                   │
│    │ • OAuth/OIDC      │           │ • Resource policies│                   │
│    │ • mTLS            │           │ • Rate limit tiers │                   │
│    └──────────────────┘           └──────────────────┘                     │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

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

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

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"

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

Don't wait for a breach to act

Get a free security assessment. Our experts will identify your vulnerabilities and create a protection plan tailored to your business.

Formal Security Models Explained: Bell-LaPadula, Biba, Clark-Wilson, and Beyond

Formal Security Models Explained: Bell-LaPadula, Biba, Clark-Wilson, and Beyond

Master the formal security models that underpin all access control systems. This comprehensive guide covers Bell-LaPadula, Biba, Clark-Wilson, Brewer-Nash, lattice-based access control, and how to choose the right model for your organization.

Biometric Authentication: Understanding FAR, FRR, and CER for Security Professionals

Biometric Authentication: Understanding FAR, FRR, and CER for Security Professionals

Master the critical metrics behind biometric authentication systems including False Acceptance Rate (FAR), False Rejection Rate (FRR), and Crossover Error Rate (CER). Learn how to evaluate, tune, and deploy biometric systems across enterprise, consumer, and high-security environments.

Database Inference & Aggregation Attacks: The Complete Defense Guide

Database Inference & Aggregation Attacks: The Complete Defense Guide

Learn how inference and aggregation attacks exploit aggregate queries and combined data to reveal protected information, and discover proven countermeasures including differential privacy, polyinstantiation, and query restriction controls.

NIST 800-88 Media Sanitization Complete Guide: Clear, Purge, and Destroy Methods Explained

NIST 800-88 Media Sanitization Complete Guide: Clear, Purge, and Destroy Methods Explained

Master NIST SP 800-88 Rev. 1 media sanitization methods including Clear, Purge, and Destroy. Covers SSD vs HDD sanitization, crypto erase, degaussing, regulatory compliance, and building a media sanitization program.

Physical Security & CPTED: The Complete Guide to Protecting Facilities, Data Centers, and Critical Assets

Physical Security & CPTED: The Complete Guide to Protecting Facilities, Data Centers, and Critical Assets

A comprehensive guide to physical security covering CPTED principles, security zones, access control, fire suppression, and environmental controls for protecting facilities and data centers.

Threat Modeling with STRIDE and DREAD: A Complete Guide to Proactive Security Architecture

Threat Modeling with STRIDE and DREAD: A Complete Guide to Proactive Security Architecture

Master threat modeling with STRIDE and DREAD frameworks to identify, classify, and prioritize security threats before they become vulnerabilities. This comprehensive guide covers data flow diagrams, mitigation mappings, MITRE ATT&CK integration, and building an enterprise threat modeling program.