jwe-decrypt
The jwe-decrypt plugin reads a five-part compact token from a request header. It selects a consumer by the token's kid and decrypts the encrypted payload with AES-256-GCM. Before proxying the request, it writes the plaintext to a configured header. You can enable the plugin on APISIX routes or services. Configure a 32-byte decryption secret on the consumer.
The token resembles JWE Compact Serialization, but it is a plugin-specific format. The implementation reads kid from the decoded header; it does not validate the alg or enc fields, and it does not use the protected-header segment as AES-GCM additional authenticated data (AAD). Standard RFC 7516 JWE libraries are therefore not directly interoperable. Generate tokens with the exact format described below, use a fixed trusted token generator, and do not treat header fields as authenticated.
The decrypted plaintext is forwarded in a request header. For sensitive plaintext, do not rely on an APISIX HTTPS upstream alone: APISIX does not verify the upstream server certificate when proxying to standard HTTPS upstreams. Send the request over an authenticated, protected network path, such as through a proxy or service mesh that validates the upstream server's identity. Restrict access to the upstream and avoid logging the configured forwarding header.
Examples
The examples below demonstrate how you can work with the jwe-decrypt plugin for different scenarios.
Decrypt Data from the Plugin Token
The following example demonstrates how to decrypt a plugin token. Generate tokens outside APISIX, configure the matching decryption key on a consumer, and create a route with jwe-decrypt to decrypt the authorization header.
- Admin API
- ADC
- Ingress Controller
Create a consumer with jwe-decrypt and configure the decryption key:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack",
"plugins": {
"jwe-decrypt": {
"key": "jack-key",
"secret": "key-length-should-be-32-chars123"
}
}
}'
Create a route with jwe-decrypt to decrypt the authorization header:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwe-decrypt-route",
"uri": "/anything/jwe",
"plugins": {
"jwe-decrypt": {
"header": "Authorization",
"forward_header": "Authorization"
}
},
"upstream": {
"type": "roundrobin",
"scheme": "https",
"nodes": {
"httpbin.org:443": 1
}
}
}'
consumers:
- username: jack
plugins:
jwe-decrypt:
key: jack-key
secret: key-length-should-be-32-chars123
services:
- name: jwe-decrypt-service
routes:
- name: jwe-decrypt-route
uris:
- /anything/jwe
plugins:
jwe-decrypt:
header: Authorization
forward_header: Authorization
upstream:
type: roundrobin
scheme: https
nodes:
- host: httpbin.org
port: 443
weight: 1
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
The following Ingress Controller configurations use public HTTPBin only with the non-sensitive demonstration payload shown on this page. Before forwarding real decrypted data, replace it with a controlled upstream and use an authenticated, protected network path. APISIX does not verify the upstream server certificate when proxying to standard HTTPS upstreams; use a proxy or service mesh that validates the upstream server's identity.
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: jack
spec:
gatewayRef:
name: apisix
plugins:
- name: jwe-decrypt
config:
key: jack-key
secret: key-length-should-be-32-chars123
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: jwe-decrypt-plugin-config
spec:
plugins:
- name: jwe-decrypt
config:
header: Authorization
forward_header: Authorization
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: jwe-decrypt-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything/jwe
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: jwe-decrypt-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
Apply the configuration to your cluster:
kubectl apply -f jwe-decrypt-ic.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: ApisixConsumer
metadata:
namespace: aic
name: jack
spec:
ingressClassName: apisix
plugins:
- name: jwe-decrypt
config:
key: jack-key
secret: key-length-should-be-32-chars123
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: jwe-decrypt-route
spec:
ingressClassName: apisix
http:
- name: jwe-decrypt-route
match:
paths:
- /anything/jwe
upstreams:
- name: httpbin-external-domain
plugins:
- name: jwe-decrypt
config:
header: Authorization
forward_header: Authorization
Apply the configuration to your cluster:
kubectl apply -f jwe-decrypt-apisix-crd.yaml
Generate plugin tokens outside APISIX by encrypting the payload with AES-256-GCM without protected-header AAD and using the consumer secret as the key. Standard RFC 7516 libraries normally authenticate the protected header as AAD and are not directly interoperable with this plugin. Use this exact token structure:
base64url(header)..base64url(iv).base64url(ciphertext).base64url(tag)
where the header is {"alg":"dir","enc":"A256GCM","kid":"<consumer-key>"}. These fields describe the intended algorithm and identify the consumer, but the current plugin does not authenticate or validate them. Use a unique, randomly generated IV for each token; never reuse an IV with the same key.
APISIX decrypts the encrypted payload and authentication tag directly with AES-256-GCM. It does not pass the protected header as AAD. A token generated with standard protected-header AAD is rejected with failed to decrypt JWE token.
Send a request to the route with the encrypted plugin token in the Authorization header. For example, the following token encrypts the payload {"uid":10000,"uname":"test"} for the consumer key jack-key with the secret configured above:
curl "http://127.0.0.1:9080/anything/jwe" -H 'Authorization: eyJraWQiOiJqYWNrLWtleSIsImFsZyI6ImRpciIsImVuYyI6IkEyNTZHQ00ifQ..vi29KBCQKcVmPwTT.VToyPMFbq-ZY05MIpntP1N3AmYeq3zELQ0B6iQ.vuTPG2ODc-DjUTjNCzfA2A'
You should see a response similar to the following, where the Authorization header shows the plaintext of the payload:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Authorization": "{\"uid\":10000,\"uname\":\"test\"}",
"Host": "127.0.0.1",
"User-Agent": "curl/8.1.2",
"X-Amzn-Trace-Id": "Root=1-6510f2c3-1586ec011a22b5094dbe1896",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"method": "GET",
"origin": "127.0.0.1, 119.143.79.94",
"url": "http://127.0.0.1/anything/jwe"
}