Sign inSign up

ipfwd/jwt-api-gateway-limiter

By ipfwd

Updated 3 days ago

API Gateway in Go: validates (optionally) JWT, extracts the limit-key from the claim (default: sub)

Image
Networking
Security
API management
0

2.4K

ipfwd/jwt-api-gateway-limiter repository overview

JWT API Limiter (Redis) — ipfwd/jwt-api-gateway-limiter

Github: https://github.com/ipfwd/jwt-api-gateway-limiter

API Gateway in Go: validates (optionally) JWT, extracts the key from the claim (default: sub), and applies rate-limit on routes (longest-prefix). Counters are stored in Redis.

Error format — JSON: {"error":{"code":429,"message":"Too many requests"}}.

Includes X-Request-Id (UUID v4), graceful shutdown, and HTTP server timeouts.

Features

  • ✅ Rate-limit by key from JWT claim (sub or any other, including nested user.id)
  • ✅ Optional JWT validation: if jwt.public_key_pem_file is empty, the signature is not verified
  • ✅ Routes as prefixes with length priority (longest-prefix)
  • ✅ Limits are set as RPS + window period (fixed window):
  • limit = ceil(rps * period_seconds)
  • ✅ Redis + Lua (atomic)
  • ✅ Headers:
  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset (epoch seconds, exact window boundary)
  • Retry-After (at 429)
  • X-Request-Id (UUID v4)
  • ✅ JSON errors
  • ✅ Graceful shutdown on SIGINT/SIGTERM

Quick Start (docker-compose)

The repository already contains docker-compose.yml.

docker compose up --build
Gateway will become available at:

http://localhost:8080

Redis:

localhost:6379

Running the Docker image

Image: ipfwd/jwt-api-gateway-limiter

Run example

docker run --rm -p 8080:8080 \
    -e CONFIG=/etc/gw/config.yaml \
    -v $(pwd)/src/config.yaml:/etc/gw/config.yaml:ro \
    -v $(pwd)/src/public.pem:/etc/gw/public.pem:ro \
    ipfwd/jwt-api-gateway-limiter:latest

Configuration

Gateway reads the configuration from the file, path is set by the CONFIG environment variable.

Example config.yaml

listen: ":8080"

redis:
  addr: "redis:6379"
  db: 0
  password: ""

jwt:
  public_key_pem_file: "" # if empty, the JWT signature is NOT verified
  algorithms: ["RS256"] # valid alg
  claim_key: "sub" # which claim to use as the key (can be "user.id")
  issuer: "" # optional
  audience: "" # optional

errors:
  limit_reached: "Too many requests"
  unavailable: "Service Unavailable"
  not_found: "Route not found"

routes:
  - id: "api"
    prefix: "/api/"
    upstream: "http://backend:80"
    period: "100s"
    rps: 10

  - id: "api-admin"
    prefix: "/api/admin/"
    upstream: "http://backend:81"
    period: "10s"
    rps: 5

  - id: "public"
    prefix: "/"
    upstream: "http://backend:82"
    period: "10s"
    rps: 1

Explanations

JWT

  • public_key_pem_file: path to PEM with the RSA/ECDSA public key (example: /etc/gw/public.pem) if "" — the signature is not verified (insecure mode, trusting the external proxy/ingress)
  • algorithms — list of acceptable alg (e.g. ["RS256"])
  • claim_key — claim used for the rate-limit key: "sub" or "user.id" (nested with a dot)
  • issuer / audience — optional checks, only validated during validation Enabled

Routes

The route is selected based on the longest prefix, for example, /api/admin/ takes precedence over /api/.

The limit is calculated as follows:

limit_per_window = ceil(rps * period_seconds)

Example:

  • rps: 5, period: 10s → limit = ceil(5 * 10) = 50 requests per 10-second window.

Error Format (JSON)

All errors are returned in the following format:

{
  "error": {
    "code": 429,
    "message": "Too many requests"
  }
}

Examples:

  • 404: {"error":{"code":404,"message":"Route not found"}}
  • 503: {"error":{"code":503,"message":"Service Unavailable"}}

Verifying with curl

curl -v -i http://localhost:8080/api/hello \
    -H "Authorization: Bearer <JWT>"

Expected Headers:

X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
Retry-After (only) to 429)
X-Request-Id

Generating a public key (RSA example)

If you need to enable validation, put the public key in PEM:

# private key
openssl genrsa -out private.pem 2048

# public key
openssl rsa -in private.pem -pubout -out public.pem

And specify in the config:

jwt:
  public_key_pem_file: "/etc/gw/public.pem"  
  algorithms: ["RS256"]

Building from source

cd src
go mod tidy
go build -o gateway ./main.go
./gateway

Development

Project Structure

  • src/main.go — gateway
  • src/config.yaml — sample config
  • src/go.mod — dependencies
  • Dockerfile — image build
  • docker-compose.yml — Redis + gateway

Security

If jwt.public_key_pem_file is empty, the JWT signature is not verified. In this mode, anyone can forge a claim_key and bypass restrictions/obtain someone else's key.

It is recommended to enable JWT validation on the gateway or place a trusted component (ingress/auth proxy) in front of it to ensure JWT validity.

License

MIT

Tag summary

Content type

Image

Digest

sha256:9aa340fc7

Size

6.5 MB

Last updated

3 days ago

docker pull ipfwd/jwt-api-gateway-limiter