Security

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

A practical guide to securing API gateways: JWT authorizers, API key management, rate limiting tiers, WAF rules, and mutual TLS for service-to-service calls.

March 9, 20265 min readShipSafer Team

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

Your API gateway is the front door to every service in your backend. It is also the primary target for abuse: credential stuffing, enumeration attacks, scraping, and DDoS all arrive through this single chokepoint. Done right, your gateway handles authentication, rate limiting, WAF filtering, and mutual TLS before a single request reaches your application code. Done wrong, it passes everything through and lets your services deal with it — which they rarely do well.

Authentication: Lambda Authorizers and JWT Verification

AWS API Gateway supports three authentication models: IAM auth, Cognito User Pools, and Lambda Authorizers. For most APIs, Lambda Authorizers (or the newer HTTP API JWT Authorizers) are the right choice.

HTTP API JWT Authorizer

For APIs where clients present JWTs (from Auth0, Cognito, or your own IdP), the HTTP API JWT authorizer handles verification without custom code:

# AWS SAM template
MyApi:
  Type: AWS::Serverless::HttpApi
  Properties:
    Auth:
      DefaultAuthorizer: MyJWTAuthorizer
      Authorizers:
        MyJWTAuthorizer:
          JwtConfiguration:
            issuer: https://your-issuer.auth0.com/
            audience:
              - https://api.example.com
          IdentitySource: "$request.header.Authorization"

API Gateway verifies the JWT signature, expiry, issuer, and audience before the request reaches your Lambda. Invalid tokens get a 401 before any of your code runs.

Lambda Authorizer for Complex Logic

When you need custom logic (API key lookup, permission scopes, tenant isolation), use a Lambda Authorizer:

export const handler = async (event: APIGatewayAuthorizerEvent) => {
  const token = event.authorizationToken?.replace('Bearer ', '');

  if (!token) {
    throw new Error('Unauthorized');
  }

  const payload = await verifyJWT(token);

  return {
    principalId: payload.sub,
    policyDocument: {
      Version: '2012-10-17',
      Statement: [{
        Action: 'execute-api:Invoke',
        Effect: payload.scopes.includes('api:read') ? 'Allow' : 'Deny',
        Resource: event.methodArn,
      }],
    },
    context: {
      userId: payload.sub,
      tenantId: payload.tenant_id,
    },
  };
};

Enable authorizer caching (5-300 seconds) to avoid verifying the same token on every request — a significant performance and cost improvement under load.

Rate Limiting: Tiers and Burst Control

Flat rate limiting (e.g., 1000 requests/minute for everyone) is too blunt. Real-world APIs need tiered limits that match business requirements: free tier users get less, enterprise customers get more, and abuse patterns are blocked regardless of tier.

AWS API Gateway Usage Plans

# Create a usage plan
aws apigateway create-usage-plan \
  --name "free-tier" \
  --throttle burstLimit=10,rateLimit=2 \
  --quota limit=1000,period=DAY

# Create an API key and associate it
aws apigateway create-api-key --name "user-abc-key" --enabled
aws apigateway create-usage-plan-key \
  --usage-plan-id PLAN_ID \
  --key-id KEY_ID \
  --key-type API_KEY

This enforces a 2 requests/second sustained rate with a burst of 10, and a hard daily cap of 1000 requests. Exceeding limits returns 429 Too Many Requests.

Kong Rate Limiting Plugin

For Kong-based gateways, the rate-limiting plugin supports per-consumer, per-IP, and per-credential policies:

plugins:
  - name: rate-limiting
    config:
      minute: 60
      hour: 1000
      policy: redis
      redis_host: redis.internal
      redis_port: 6379
      fault_tolerant: false
      hide_client_headers: false

Setting policy: redis enables distributed rate limiting across multiple Kong nodes — essential for any horizontally scaled deployment. Without Redis, each node maintains its own counters and the effective rate limit is N × per-node-limit.

WAF Rules: Blocking Common Attack Patterns

A Web Application Firewall at the gateway layer blocks malicious requests before they reach application code. AWS WAF integrates natively with API Gateway.

AWS WAF Managed Rule Groups

resource "aws_wafv2_web_acl" "api_waf" {
  name  = "api-gateway-waf"
  scope = "REGIONAL"

  default_action {
    allow {}
  }

  rule {
    name     = "AWSManagedRulesCommonRuleSet"
    priority = 1
    override_action { none {} }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }
    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "CommonRuleSet"
      sampled_requests_enabled   = true
    }
  }

  rule {
    name     = "RateLimitRule"
    priority = 2
    action { block {} }

    statement {
      rate_based_statement {
        limit              = 2000
        aggregate_key_type = "IP"
      }
    }
    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "RateLimit"
      sampled_requests_enabled   = true
    }
  }
}

The AWSManagedRulesCommonRuleSet blocks OWASP Top 10 patterns including SQLi, XSS, and path traversal. The rate-based rule adds a per-IP circuit breaker independent of usage plans.

Custom Rules for API-Specific Patterns

WAF managed rules are good for known attack signatures, but your API may need custom rules. For example, blocking requests with anomalous Content-Type headers for your JSON API:

# Block requests that claim to be JSON but have suspicious content
Rule: body contains 'UNION SELECT' or body contains '<script>'

Mutual TLS for Service-to-Service APIs

For internal APIs (service mesh bypass, partner integrations, machine-to-machine), mutual TLS (mTLS) ensures both the client and server verify each other's identity. API Gateway HTTP APIs support mTLS natively.

Configuring mTLS on API Gateway

# Upload your CA certificate bundle to S3
aws s3 cp ca-bundle.pem s3://your-truststore-bucket/ca-bundle.pem

# Enable mTLS on the API custom domain
aws apigatewayv2 update-domain-name \
  --domain-name api.internal.example.com \
  --domain-name-configurations \
    CertificateArn=arn:aws:acm:us-east-1:123456789:certificate/abc,\
    EndpointType=REGIONAL \
  --mutual-tls-authentication \
    TruststoreUri=s3://your-truststore-bucket/ca-bundle.pem

With mTLS enabled, clients must present a certificate signed by your CA. Requests without a valid client certificate are rejected at the TLS handshake layer — before any HTTP processing.

Request Validation

API Gateway can validate request bodies and parameters against a model schema, rejecting malformed requests before they reach your Lambda:

{
  "type": "object",
  "required": ["email", "amount"],
  "properties": {
    "email": {
      "type": "string",
      "format": "email"
    },
    "amount": {
      "type": "number",
      "minimum": 0.01,
      "maximum": 10000
    }
  },
  "additionalProperties": false
}

Enabling additionalProperties: false prevents parameter pollution attacks where extra fields might be interpreted in unexpected ways by downstream services.

Logging and Observability

All of this is only useful if you can see what is happening. Enable access logging to CloudWatch Logs with a structured format:

$context.requestId $context.identity.sourceIp $context.requestTime $context.httpMethod $context.path $context.status $context.responseLength $context.authorizer.principalId

Feed these logs into your SIEM and create alerts for:

  • 401/403 rate spikes (credential stuffing or auth bypass attempts)
  • 429 rate limit hits from a single IP
  • WAF block counts increasing
  • Unusual request patterns (scanning, enumeration)

A well-configured API gateway does not just route traffic — it is your first and most consistent security control for every API endpoint you expose.

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.