Gateway Configuration
Configuration examples for popular API gateways. Each example covers:
- JWT validation
- Header injection based on token claims
- Proxying to Minimal
- Stripping internal headers from responses
Gateway Selection Guide
| Gateway | Best For | Complexity | JWT Support |
|---|---|---|---|
| Caddy | New projects, simplicity | Low | Plugin required |
| Kong | Enterprise, plugin ecosystem | Medium | Built-in |
| Traefik | Docker/K8s native | Medium | ForwardAuth or plugin |
| Nginx | Existing Nginx infrastructure | High | Lua 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.