jwt-auth
The jwt-auth plugin supports the use of JSON Web Token (JWT) as a mechanism for clients to authenticate themselves before accessing upstream resources.
Once enabled, JWT credentials are configured on consumers, and clients carry a signed token to identify themselves to APISIX. The token can be included in the request URL query string, request header, or cookie. APISIX will then verify the token to determine if a request should be allowed or denied to access upstream resources.
When a consumer is successfully authenticated, APISIX adds additional headers, such as X-Consumer-Username, X-Credential-Identifier, and other consumer custom headers if configured, to the request, before proxying it to the upstream service. The upstream service will be able to differentiate between consumers and implement additional logics as needed. If any of these values is not available, the corresponding header will not be added.
When consumers are configured using the Ingress Controller, the consumer name is generated in the format namespace_consumername. As a result, the X-Consumer-Username header will also follow this format instead of just consumername.
Examples
The examples below demonstrate how you can work with the jwt-auth plugin for different scenarios.
Generate JWTs Locally
The examples generate JWTs locally with OpenSSL. Define these helpers once in the current shell:
base64url() {
openssl base64 -A | tr '+/' '-_' | tr -d '='
}
sign_hs256() {
local secret="$1"
local payload="$2"
local encoded_header encoded_payload signature
encoded_header=$(printf '%s' '{"alg":"HS256","typ":"JWT"}' | base64url)
encoded_payload=$(printf '%s' "$payload" | base64url)
signature=$(printf '%s' "$encoded_header.$encoded_payload" | \
openssl dgst -sha256 -hmac "$secret" -binary | base64url)
printf '%s.%s.%s\n' "$encoded_header" "$encoded_payload" "$signature"
}
sign_rs256() {
local private_key="$1"
local payload="$2"
local encoded_header encoded_payload signature
encoded_header=$(printf '%s' '{"alg":"RS256","typ":"JWT"}' | base64url)
encoded_payload=$(printf '%s' "$payload" | base64url)
signature=$(printf '%s' "$encoded_header.$encoded_payload" | \
openssl dgst -sha256 -sign "$private_key" | base64url)
printf '%s.%s.%s\n' "$encoded_header" "$encoded_payload" "$signature"
}
The local examples omit expiration claims so the commands remain reproducible. For production tokens, use a short exp lifetime. Configure claims_to_verify when an exp or nbf claim should be required; listed claims must be present and valid.
Use JWT for Consumer Authentication
The following example demonstrates how to implement JWT for consumer key authentication.
- Admin API
- ADC
- Ingress Controller
Create a consumer jack:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'
Create jwt-auth credential for the consumer:
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret-that-is-very-long"
}
}
}'
Create a route with jwt-auth plugin:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/headers",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Create a consumer with jwt-auth credential and a route with jwt-auth plugin configured as such:
consumers:
- username: jack
labels:
docs-example: jwt-auth
credentials:
- name: jwt-auth
type: jwt-auth
config:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
services:
- name: jwt-auth-service
labels:
docs-example: jwt-auth
routes:
- name: jwt-route
uris:
- /headers
plugins:
jwt-auth: {}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
ADC reconciles the selected top-level resource types as desired state. The label selector limits this example to its own labeled services and consumers; credentials remain nested under their parent consumers. Preview the scoped changes and confirm that they contain no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Synchronize the reviewed configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
- Gateway API
- APISIX CRD
Create a consumer with jwt-auth credential and a route with jwt-auth plugin configured as such:
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: jack
spec:
gatewayRef:
name: apisix
credentials:
- type: jwt-auth
name: primary-cred
config:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: jwt-auth-plugin-config
spec:
plugins:
- name: jwt-auth
config:
_meta:
disable: false
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: jwt-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /headers
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: jwt-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
Create a consumer with jwt-auth credential using HS256 and a route with jwt-auth plugin enabled as such:
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: jack
spec:
ingressClassName: apisix
authParameter:
jwtAuth:
value:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: jwt-route
spec:
ingressClassName: apisix
http:
- name: jwt-route
match:
paths:
- /headers
upstreams:
- name: httpbin-external-domain
plugins:
- name: jwt-auth
enable: true
config:
_meta:
disable: false
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
Generate a JWT locally with the key and secret configured for jack:
jwt_token=$(sign_hs256 \
'jack-hs256-secret-that-is-very-long' \
'{"key":"jack-key"}')
export jwt_token
Send a request to the route with the JWT in the Authorization header:
curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"
You should receive an HTTP/1.1 200 OK response similar to the following:
{
"headers": {
"Accept": "*/*",
"Authorization": "...",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66ea951a-4d740d724bd2a44f174d4daf",
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-jwt-auth",
"X-Forwarded-Host": "127.0.0.1"
}
}
Generate a structurally valid token with an incorrect signing secret, then send it to the route:
invalid_jwt_token=$(sign_hs256 \
'incorrect-hs256-secret-that-is-very-long' \
'{"key":"jack-key"}')
curl -i "http://127.0.0.1:9080/headers" -H "Authorization: $invalid_jwt_token"
You should receive an HTTP/1.1 401 Unauthorized response similar to the following:
{"message":"failed to verify jwt"}
Carry JWT in Request Header, Query String, or Cookie
The following example demonstrates how to accept JWT in specified header, query string, and cookie.
- Admin API
- ADC
- Ingress Controller
Create a consumer jack:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'
Create jwt-auth credential for the consumer:
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret-that-is-very-long"
}
}
}'
Create a route with jwt-auth plugin, and specify the request parameters carrying the token:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/get",
"plugins": {
"jwt-auth": {
"header": "jwt-auth-header",
"query": "jwt-query",
"cookie": "jwt-cookie"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Create a consumer with jwt-auth credential and a route with jwt-auth plugin configured as such:
consumers:
- username: jack
labels:
docs-example: jwt-auth
credentials:
- name: jwt-auth
type: jwt-auth
config:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
services:
- name: jwt-auth-service
labels:
docs-example: jwt-auth
routes:
- name: jwt-route
uris:
- /get
plugins:
jwt-auth:
header: jwt-auth-header
query: jwt-query
cookie: jwt-cookie
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
ADC reconciles the selected top-level resource types as desired state. The label selector limits this example to its own labeled services and consumers; credentials remain nested under their parent consumers. Preview the scoped changes and confirm that they contain no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Synchronize the reviewed configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
- Gateway API
- APISIX CRD
Create a consumer with jwt-auth credential and a route with jwt-auth plugin configured as such:
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: jack
spec:
gatewayRef:
name: apisix
credentials:
- type: jwt-auth
name: primary-cred
config:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: jwt-auth-plugin-config
spec:
plugins:
- name: jwt-auth
config:
_meta:
disable: false
header: jwt-auth-header
query: jwt-query
cookie: jwt-cookie
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: jwt-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /get
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: jwt-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
Create a consumer with jwt-auth credential and a route with jwt-auth plugin configured as such:
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: jack
spec:
ingressClassName: apisix
authParameter:
jwtAuth:
value:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: jwt-route
spec:
ingressClassName: apisix
http:
- name: jwt-route
match:
paths:
- /get
upstreams:
- name: httpbin-external-domain
plugins:
- name: jwt-auth
enable: true
config:
header: jwt-auth-header
query: jwt-query
cookie: jwt-cookie
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
Generate a JWT locally with the key and secret configured for jack:
jwt_token=$(sign_hs256 \
'jack-hs256-secret-that-is-very-long' \
'{"key":"jack-key"}')
export jwt_token
Verify With JWT in Header
Sending request with JWT in the header:
curl -i "http://127.0.0.1:9080/get" -H "jwt-auth-header: ${jwt_token}"
You should receive an HTTP/1.1 200 OK response similar to the following:
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"Jwt-Auth-Header": "...",
...
},
...
}
Verify With JWT in Query String
Sending request with JWT in the query string:
curl -i "http://127.0.0.1:9080/get?jwt-query=${jwt_token}"
You should receive an HTTP/1.1 200 OK response similar to the following:
{
"args": {
"jwt-query": "..."
},
"headers": {
"Accept": "*/*",
...
},
"origin": "127.0.0.1, 183.17.233.107",
"url": "http://127.0.0.1/get?jwt-query=..."
}
Verify With JWT in Cookie
Sending request with JWT in the cookie:
curl -i "http://127.0.0.1:9080/get" --cookie jwt-cookie=${jwt_token}
You should receive an HTTP/1.1 200 OK response similar to the following:
{
"args": {},
"headers": {
"Accept": "*/*",
"Cookie": "jwt-cookie=...",
...
},
...
}
Manage Secrets in Environment Variables
The following example demonstrates how to save jwt-auth consumer key to an environment variable and reference it in configuration.
APISIX supports referencing system and user environment variables configured through the NGINX env directive.
Save the key to an environment variable:
export JACK_JWT_SECRET=jack-hs256-secret-that-is-very-long
If you are running APISIX in Docker, you should set the environment variable using the -e flag when starting the container. If you are running APISIX on Kubernetes, see the Ingress Controller tab for more details.
- Admin API
- ADC
Create a consumer jack:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'
Create jwt-auth credential for the consumer and reference the environment variable:
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "$env://JACK_JWT_SECRET"
}
}
}'
Create a route with jwt-auth enabled:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/get",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Create a consumer with jwt-auth credential referencing an environment variable and a route with jwt-auth plugin enabled as such:
consumers:
- username: jack
labels:
docs-example: jwt-auth
credentials:
- name: jwt-auth
type: jwt-auth
config:
key: jack-key
secret: $env://JACK_JWT_SECRET
services:
- name: jwt-auth-service
labels:
docs-example: jwt-auth
routes:
- name: jwt-route
uris:
- /get
plugins:
jwt-auth: {}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
ADC reconciles the selected top-level resource types as desired state. The label selector limits this example to its own labeled services and consumers; credentials remain nested under their parent consumers. Preview the scoped changes and confirm that they contain no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Synchronize the reviewed configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Generate a JWT locally with the key and secret configured for jack:
jwt_token=$(sign_hs256 \
'jack-hs256-secret-that-is-very-long' \
'{"key":"jack-key"}')
export jwt_token
Sending request with JWT in the header:
curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${jwt_token}"
You should receive an HTTP/1.1 200 OK response.
Store a JWT Secret in HashiCorp Vault
HashiCorp Vault provides centralized external storage for secrets. The following example stores a JWT signing secret in Vault instead of directly in a jwt-auth credential and references it from the gateway.
Vault dev mode stores data in memory and uses a root token. Use this setup only for local testing. In production, run a production deployment of Vault and give the gateway a token with read access only to the required secret paths.
Set GATEWAY_CONTAINER to the name of the running APISIX or API7 Gateway container. Create a dedicated Docker network and connect the gateway to it:
export GATEWAY_CONTAINER=replace-with-gateway-container-name
docker network create gateway-vault-net
docker network connect gateway-vault-net "$GATEWAY_CONTAINER"
Start a Vault development server on the same network:
docker run -d \
--name gateway-vault \
--network gateway-vault-net \
--cap-add IPC_LOCK \
-e VAULT_DEV_ROOT_TOKEN_ID=root \
-e VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 \
hashicorp/vault:1.21.4 \
vault server -dev
The gateway's Vault secret provider reads from Vault KV version 1. Enable a KV version 1 secrets engine at kv/:
docker exec gateway-vault sh -c \
"VAULT_TOKEN='root' VAULT_ADDR='http://127.0.0.1:8200' vault secrets enable -path=kv -version=1 kv"
You should see a response similar to the following:
Success! Enabled the kv secrets engine at: kv/
Store the signing secret under kv/apisix/jack:
docker exec gateway-vault sh -c \
"VAULT_TOKEN='root' VAULT_ADDR='http://127.0.0.1:8200' vault kv put kv/apisix/jack jwt-secret=vault-hs256-secret-that-is-very-long"
You should see a response similar to the following:
Success! Data written to: kv/apisix/jack
Create a gateway secret resource with the Vault connection details. ADC does not currently synchronize secret resources, so create this resource through the Admin API:
curl "http://127.0.0.1:9180/apisix/admin/secrets/vault/jwt" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "http://gateway-vault:8200",
"prefix": "kv/apisix",
"token": "root"
}'
The Vault container is reachable by name because it runs on the same Docker network as the gateway.
The secret resource is required for both configuration methods below. ADC does not currently synchronize secret resources, but it can synchronize credentials that reference an existing secret resource.
- Admin API
- ADC
Create a consumer jack:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'
Create jwt-auth credential for the consumer and reference the secret:
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jwt-vault-key",
"secret": "$secret://vault/jwt/jack/jwt-secret"
}
}
}'
Create a route with jwt-auth enabled:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/get",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Create adc.yaml with the consumer, Vault-backed credential, and route:
consumers:
- username: jack
labels:
docs-example: jwt-auth
credentials:
- id: cred-jack-jwt-auth
name: jwt-auth
type: jwt-auth
config:
key: jwt-vault-key
secret: $secret://vault/jwt/jack/jwt-secret
services:
- name: jwt-auth-service
labels:
docs-example: jwt-auth
routes:
- name: jwt-route
uris:
- /get
plugins:
jwt-auth: {}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
ADC reconciles the selected top-level resource types as desired state. The label selector limits this example to its own labeled services and consumers; credentials remain nested under their parent consumers. Preview the scoped changes and confirm that they contain no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Synchronize the reviewed configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
In both methods, the reference uses the jwt secret resource to read the jwt-secret field from kv/apisix/jack.
Generate a JWT locally with the key and secret stored in Vault:
jwt_token=$(sign_hs256 \
'vault-hs256-secret-that-is-very-long' \
'{"key":"jwt-vault-key"}')
export jwt_token
Send a request with the token in the header:
curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${jwt_token}"
You should receive an HTTP/1.1 200 OK response. The response body should include headers that identify the authenticated consumer:
{
"headers": {
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-jwt-auth"
}
}
Sign JWT with RS256 Algorithm
The following example uses RS256 to sign and validate JWTs. The private key remains on the local machine; the gateway stores only the public key used for verification.
Generate a 2048-bit RSA private key and extract the corresponding public key in PEM format:
openssl genrsa -out jwt-rsa256-private.pem 2048
openssl rsa -in jwt-rsa256-private.pem -pubout -out jwt-rsa256-public.pem
Generate the JWT locally with the private key:
jwt_token=$(sign_rs256 jwt-rsa256-private.pem '{"key":"jack-key"}')
export jwt_token
Use the generated jwt-rsa256-public.pem contents for public_key in the configuration method you choose below.
- Admin API
- ADC
- Ingress Controller
Create a consumer jack:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'
Create jwt-auth credential for the consumer and configure the RSA keys:
public_key=$(awk '{printf "%s\\n", $0}' jwt-rsa256-public.pem)
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"algorithm": "RS256",
"public_key": "$public_key"
}
}
}
EOF
❶ Configure the consumer key to be jack-key.
❷ Configure the JWT signing algorithm to be RS256.
❸ Configure the RSA public key.
Create a route with the jwt-auth plugin:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/headers",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Create adc.yaml with the generated public key, consumer credential, and route:
public_key_yaml=$(sed 's/^/ /' jwt-rsa256-public.pem)
cat > adc.yaml <<EOF
consumers:
- username: jack
labels:
docs-example: jwt-auth
credentials:
- name: jwt-auth
type: jwt-auth
config:
key: jack-key
algorithm: RS256
public_key: |
${public_key_yaml}
services:
- name: jwt-auth-service
labels:
docs-example: jwt-auth
routes:
- name: jwt-route
uris:
- /headers
plugins:
jwt-auth: {}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
EOF
ADC reconciles the selected top-level resource types as desired state. The label selector limits this example to its own labeled services and consumers; credentials remain nested under their parent consumers. Preview the scoped changes and confirm that they contain no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Synchronize the reviewed configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
- Gateway API
- APISIX CRD
Create jwt-auth-ic.yaml with the generated public key, consumer credential, and route:
public_key_yaml=$(sed 's/^/ /' jwt-rsa256-public.pem)
cat > jwt-auth-ic.yaml <<EOF
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: jack
spec:
gatewayRef:
name: apisix
credentials:
- type: jwt-auth
name: primary-cred
config:
key: jack-key
algorithm: RS256
public_key: |
${public_key_yaml}
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: jwt-auth-plugin-config
spec:
plugins:
- name: jwt-auth
config:
_meta:
disable: false
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: jwt-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /headers
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: jwt-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
EOF
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
Create jwt-auth-ic.yaml with the generated public key, consumer credential, and route:
public_key_yaml=$(sed 's/^/ /' jwt-rsa256-public.pem)
cat > jwt-auth-ic.yaml <<EOF
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: jack
spec:
ingressClassName: apisix
authParameter:
jwtAuth:
value:
key: jack-key
algorithm: RS256
public_key: |
${public_key_yaml}
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: jwt-route
spec:
ingressClassName: apisix
http:
- name: jwt-route
match:
paths:
- /headers
upstreams:
- name: httpbin-external-domain
plugins:
- name: jwt-auth
enable: true
config:
_meta:
disable: false
EOF
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
To verify, send a request to the route with the JWT in the Authorization header:
curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"
You should receive an HTTP/1.1 200 OK response.
Add Consumer Custom ID to Header
The following example demonstrates how you can attach a consumer custom ID to authenticated request in the Consumer-Custom-Id header, which can be used to implement additional logics as needed.
- Admin API
- ADC
- Ingress Controller
Create a consumer jack with a custom ID label:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack",
"labels": {
"custom_id": "495aec6a"
}
}'
Create jwt-auth credential for the consumer:
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret-that-is-very-long"
}
}
}'
Create a route with jwt-auth:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-auth-route",
"uri": "/anything",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Create a consumer with jwt-auth credential and a route with jwt-auth plugin enabled as such:
consumers:
- username: jack
labels:
docs-example: jwt-auth
custom_id: "495aec6a"
credentials:
- name: jwt-auth
type: jwt-auth
config:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
services:
- name: jwt-auth-service
labels:
docs-example: jwt-auth
routes:
- name: jwt-auth-route
uris:
- /anything
plugins:
jwt-auth: {}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
ADC reconciles the selected top-level resource types as desired state. The label selector limits this example to its own labeled services and consumers; credentials remain nested under their parent consumers. Preview the scoped changes and confirm that they contain no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Synchronize the reviewed configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Create a consumer with jwt-auth credential and a route with jwt-auth plugin enabled:
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: jack
labels:
custom_id: "495aec6a"
spec:
gatewayRef:
name: apisix
credentials:
- type: jwt-auth
name: primary-cred
config:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: jwt-auth-plugin-config
spec:
plugins:
- name: jwt-auth
config:
_meta:
disable: false
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: jwt-auth-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: jwt-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: jack
labels:
custom_id: "495aec6a"
spec:
ingressClassName: apisix
authParameter:
jwtAuth:
value:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: jwt-auth-route
spec:
ingressClassName: apisix
http:
- name: jwt-auth-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: jwt-auth
enable: true
config:
_meta:
disable: false
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
Generate a JWT locally with the key and secret configured for jack:
jwt_token=$(sign_hs256 \
'jack-hs256-secret-that-is-very-long' \
'{"key":"jack-key"}')
export jwt_token
To verify, send a request to the route with the JWT in the Authorization header:
curl -i "http://127.0.0.1:9080/anything" -H "Authorization: ${jwt_token}"
You should see an HTTP/1.1 200 OK response similar to the following:
{
"headers": {
"Accept": "*/*",
"Authorization": "...",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-6873b19d-329331db76e5e7194c942b47",
"X-Consumer-Custom-Id": "495aec6a",
"X-Consumer-Username": "aic_jack",
"X-Forwarded-Host": "127.0.0.1"
},
"url": "http://127.0.0.1/anything"
}
If you would like to attach more consumer custom headers to authenticated requests, see the attach-consumer-label plugin.
Rate Limit with Anonymous Consumer
The following example demonstrates how you can configure different rate limiting policies by regular and anonymous consumers, where the anonymous consumer does not need to authenticate and has less quota.
- Admin API
- ADC
- Ingress Controller
Create a regular consumer jack and configure the limit-count plugin to allow for a quota of 3 within a 30-second window:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack",
"plugins": {
"limit-count": {
"count": 3,
"time_window": 30,
"rejected_code": 429,
"policy": "local"
}
}
}'
Create the jwt-auth credential for the consumer jack:
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret-that-is-very-long"
}
}
}'
Create an anonymous user anonymous and configure the limit-count plugin to allow for a quota of 1 within a 30-second window:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429,
"policy": "local"
}
}
}'
Create a route and configure the jwt-auth plugin to accept anonymous consumer anonymous from bypassing the authentication:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-auth-route",
"uri": "/anything",
"plugins": {
"jwt-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Configure consumers with different rate limits and a route that accepts anonymous users:
consumers:
- username: jack
labels:
docs-example: jwt-auth
plugins:
limit-count:
count: 3
time_window: 30
rejected_code: 429
policy: local
credentials:
- name: jwt-auth
type: jwt-auth
config:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
- username: anonymous
labels:
docs-example: jwt-auth
plugins:
limit-count:
count: 1
time_window: 30
rejected_code: 429
policy: local
services:
- name: anonymous-rate-limit-service
labels:
docs-example: jwt-auth
routes:
- name: jwt-auth-route
uris:
- /anything
plugins:
jwt-auth:
anonymous_consumer: anonymous
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
ADC reconciles the selected top-level resource types as desired state. The label selector limits this example to its own labeled services and consumers; credentials remain nested under their parent consumers. Preview the scoped changes and confirm that they contain no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
Synchronize the reviewed configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=jwt-auth
- Gateway API
- APISIX CRD
Configure consumers with different rate limits and a route that accepts anonymous users:
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: jack
spec:
gatewayRef:
name: apisix
credentials:
- type: jwt-auth
name: primary-key
config:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
plugins:
- name: limit-count
config:
count: 3
time_window: 30
rejected_code: 429
policy: local
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: anonymous
spec:
gatewayRef:
name: apisix
plugins:
- name: limit-count
config:
count: 1
time_window: 30
rejected_code: 429
policy: local
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: jwt-auth-plugin-config
spec:
plugins:
- name: jwt-auth
config:
anonymous_consumer: aic_anonymous # namespace_consumername
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: jwt-auth-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: jwt-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
Configure consumers with different rate limits and a route that accepts anonymous users:
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: jack
spec:
ingressClassName: apisix
authParameter:
jwtAuth:
value:
key: jack-key
secret: jack-hs256-secret-that-is-very-long
plugins:
- name: limit-count
enable: true
config:
count: 3
time_window: 30
rejected_code: 429
policy: local
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: anonymous
spec:
ingressClassName: apisix
plugins:
- name: limit-count
enable: true
config:
count: 1
time_window: 30
rejected_code: 429
policy: local
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: jwt-auth-route
spec:
ingressClassName: apisix
http:
- name: jwt-auth-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: jwt-auth
enable: true
config:
anonymous_consumer: aic_anonymous
Apply the configuration to your cluster:
kubectl apply -f jwt-auth-ic.yaml
Generate a JWT locally with the key and secret configured for jack:
jwt_token=$(sign_hs256 \
'jack-hs256-secret-that-is-very-long' \
'{"key":"jack-key"}')
export jwt_token
To verify the rate limiting, send five consecutive requests with jack's JWT:
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -H "Authorization: ${jwt_token}" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
You should see the following response, showing that out of the 5 requests, 3 requests were successful (status code 200) while the others were rejected (status code 429).
200: 3, 429: 2
Send five anonymous requests:
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
You should see the following response, showing that only one request was successful:
200: 1, 429: 4