Skip to main content

Gateway Configuration

Configuration examples for popular API gateways. Each example covers:

  1. JWT validation
  2. Header injection based on token claims
  3. Proxying to Minimal
  4. Stripping internal headers from responses

Gateway Selection Guide

GatewayBest ForComplexityJWT Support
CaddyNew projects, simplicityLowPlugin required
KongEnterprise, plugin ecosystemMediumBuilt-in
TraefikDocker/K8s nativeMediumForwardAuth or plugin
NginxExisting Nginx infrastructureHighLua scripting

Recommendation:

  • Starting fresh → Caddy
  • Enterprise with plugin needs → Kong
  • Container orchestration → Traefik
  • Already on Nginx → stick with Nginx + Lua

JWT Claims Structure

All gateway examples expect the JWT to contain these claims:

{
"sub": "01KBY3K9NDC5XW523M2V1Z0373",
"org_id": "01KC11784DW133S3THR7JTQNQ1",
"project_id": "01KC11KZ6VDMMKJZ4YWQYKYZ6Z",
"space_id": "01KBY3K97GMAJFBQ0PP7158NZH",
"roles": "admin,editor"
}

Caddy

Caddy is the simplest option for teams starting fresh. This example uses the caddy-jwt plugin for token validation.

Caddyfile

{
order jwt before reverse_proxy
}

api.example.com {
# JWT validation
jwt {
primary yes
issuer https://auth.example.com
audience api
jwks_url https://auth.example.com/.well-known/jwks.json
}

# Route to Minimal
reverse_proxy localhost:8080 {
# Inject headers from JWT claims
header_up X-Org-Id {http.auth.claims.org_id}
header_up X-Project-Id {http.auth.claims.project_id}
header_up X-Space-Id {http.auth.claims.space_id}
header_up X-User-Id {http.auth.claims.sub}
header_up X-User-Roles {http.auth.claims.roles}

# Strip internal headers from response
header_down -X-Org-Id
header_down -X-Project-Id
header_down -X-Space-Id
header_down -X-User-Id
header_down -X-User-Roles
}
}

Kong (OSS)

Kong provides plugin-based architecture suitable for enterprise deployments.

note

The $(jwt.claims.field) header injection syntax applies to Kong with the request-transformer plugin. Syntax may vary depending on your Kong version and installed plugins.

kong.yml (Declarative Config)

_format_version: "3.0"

services:
- name: minimal-service
url: http://localhost:8080
routes:
- name: minimal-route
paths:
- /api
strip_path: true

plugins:
# JWT validation
- name: jwt
service: minimal-service
config:
key_claim_name: kid
claims_to_verify:
- exp

# Header injection from JWT claims
- name: request-transformer
service: minimal-service
config:
add:
headers:
- X-Org-Id:$(jwt.claims.org_id)
- X-Project-Id:$(jwt.claims.project_id)
- X-Space-Id:$(jwt.claims.space_id)
- X-User-Id:$(jwt.claims.sub)
- X-User-Roles:$(jwt.claims.roles)

# Strip internal headers from response
- name: response-transformer
service: minimal-service
config:
remove:
headers:
- X-Org-Id
- X-Project-Id
- X-Space-Id
- X-User-Id
- X-User-Roles

consumers:
- username: api-consumer

jwt_secrets:
- consumer: api-consumer
key: your-key-id
algorithm: RS256
rsa_public_key: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

Using OpenID Connect (Kong Enterprise or Plugin)

plugins:
- name: openid-connect
service: minimal-service
config:
issuer: https://auth.example.com/
client_id: your-client-id
client_secret: your-client-secret
auth_methods:
- bearer
upstream_headers_claims:
- org_id:X-Org-Id
- project_id:X-Project-Id
- space_id:X-Space-Id
- sub:X-User-Id
- roles:X-User-Roles

Traefik

Traefik integrates well with Docker and Kubernetes environments.

traefik.yml (Static Config)

entryPoints:
web:
address: ":80"
websecure:
address: ":443"

providers:
file:
filename: /etc/traefik/dynamic.yml

api:
dashboard: true

dynamic.yml (Dynamic Config)

http:
routers:
minimal-router:
rule: "Host(`api.example.com`)"
service: minimal-service
entryPoints:
- websecure
middlewares:
- jwt-auth
- inject-headers
- strip-response-headers
tls: {}

services:
minimal-service:
loadBalancer:
servers:
- url: "http://localhost:8080"

middlewares:
# JWT validation using ForwardAuth
jwt-auth:
forwardAuth:
address: "http://auth-service:3000/verify"
authResponseHeaders:
- X-Org-Id
- X-Project-Id
- X-Space-Id
- X-User-Id
- X-User-Roles

# Header injection
inject-headers:
headers:
customRequestHeaders:
X-Forwarded-By: "traefik-gateway"

# Strip internal headers from response
strip-response-headers:
headers:
customResponseHeaders:
X-Org-Id: ""
X-Project-Id: ""
X-Space-Id: ""
X-User-Id: ""
X-User-Roles: ""

ForwardAuth Service (Node.js)

const jwt = require('jsonwebtoken');

module.exports = (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '');

if (!token) {
return res.status(401).send('Missing token');
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);

res.setHeader('X-Org-Id', decoded.org_id);
res.setHeader('X-Project-Id', decoded.project_id);
res.setHeader('X-Space-Id', decoded.space_id);
res.setHeader('X-User-Id', decoded.sub);
res.setHeader('X-User-Roles', decoded.roles);

res.status(200).send('OK');
} catch (err) {
res.status(401).send('Invalid token');
}
};

Nginx

For teams already using Nginx with lua-nginx-module.

nginx.conf

http {
lua_package_path "/usr/local/openresty/lualib/?.lua;;";

upstream minimal {
server 127.0.0.1:8080;
}

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

ssl_certificate /etc/ssl/certs/api.crt;
ssl_certificate_key /etc/ssl/private/api.key;

location /api/ {
access_by_lua_block {
local jwt = require "resty.jwt"
local validators = require "resty.jwt-validators"

local auth_header = ngx.var.http_authorization
if not auth_header then
ngx.status = 401
ngx.say("Missing Authorization header")
return ngx.exit(401)
end

local token = string.match(auth_header, "Bearer%s+(.+)")
local jwt_obj = jwt:verify(os.getenv("JWT_SECRET"), token, {
exp = validators.is_not_expired(),
})

if not jwt_obj.verified then
ngx.status = 401
ngx.say("Invalid token")
return ngx.exit(401)
end

ngx.req.set_header("X-Org-Id", jwt_obj.payload.org_id)
ngx.req.set_header("X-Project-Id", jwt_obj.payload.project_id)
ngx.req.set_header("X-Space-Id", jwt_obj.payload.space_id)
ngx.req.set_header("X-User-Id", jwt_obj.payload.sub)
ngx.req.set_header("X-User-Roles", jwt_obj.payload.roles)
}

proxy_pass http://minimal/;

proxy_hide_header X-Org-Id;
proxy_hide_header X-Project-Id;
proxy_hide_header X-Space-Id;
proxy_hide_header X-User-Id;
proxy_hide_header X-User-Roles;
}
}
}

For a complete Nginx reference without Lua, see nginx Reference.