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.