jwe-decrypt
The jwe-decrypt plugin reads a five-part JWE Compact Serialization token from a request header. It selects a consumer (opens in Apache APISIX docs) by the protected header's kid and decrypts the payload with direct encryption and A256GCM using the consumer's 32-byte secret. Before proxying the request, it forwards the plaintext in a configured header. You can enable the plugin on APISIX routes (opens in Apache APISIX docs) or services (opens in Apache APISIX docs).
The plugin is available in APISIX 3.19.0 and API7 Enterprise 3.10.7, but not API7 Enterprise 3.9.21. Protected-header handling depends on the gateway version.
APISIX 3.19.0 authenticates the encoded protected header as the AES-GCM additional authenticated data (AAD), as RFC 7516 specifies. API7 Enterprise 3.10.7 has the same behavior. A token produced by a standard JWE library is therefore accepted. Legacy tokens generated without AAD are also accepted, so tokens issued before the upgrade keep working. This compatibility fallback does not authenticate the protected header of a legacy token.
In APISIX 3.19.0 and API7 Enterprise 3.10.7, alg and enc are also validated. A token carrying an alg other than dir, or an enc other than A256GCM, is rejected with HTTP 400 instead of failing later with a decryption error. A token that omits either field is still accepted.
On earlier APISIX releases, the protected header is used neither as AAD nor for validation. alg and enc are ignored, and standard RFC 7516 libraries are not directly interoperable. Generate tokens in the exact format described below, from a fixed trusted generator, and do not treat header fields as authenticated.
caution
The decrypted plaintext is forwarded in a request header. For sensitive plaintext, configure certificate verification and a trusted CA store so the gateway validates the upstream server's identity. Restrict access to the upstream and avoid logging the configured forwarding header.
See Configure HTTPS Between APISIX and Upstream Services (opens in Apache APISIX docs) for the verification procedure.
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.
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: 1Synchronize the configuration to the gateway:
adc sync -f adc.yamlThe 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. An upstream server certificate is verified only when the upstream enables verification and supplies its CA certificates. Otherwise, use a proxy or service mesh that validates the upstream server's identity.
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: 80Apply the configuration to your cluster:
kubectl apply -f jwe-decrypt-ic.yamlapiVersion: 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: AuthorizationApply the configuration to your cluster:
kubectl apply -f jwe-decrypt-apisix-crd.yamlGenerate tokens outside APISIX, encrypting the payload with AES-256-GCM and using the consumer secret as the key.
From API7 Enterprise version 3.10.7, the encoded protected header is authenticated as the AAD, so a standard RFC 7516 library produces an accepted token. Where that is not available, the header is not used as AAD and the token must be built without it. Both forms are accepted from 3.10.7. The token structure is:
base64url(header)..base64url(iv).base64url(ciphertext).base64url(tag)where the header is {"alg":"dir","enc":"A256GCM","kid":"<consumer-key>"}. kid identifies the consumer.
From API7 Enterprise version 3.10.7, alg and enc are validated, and a value other than dir or A256GCM is rejected. The encoded header is authenticated as the AAD. Where that is not available, the two fields are neither validated nor authenticated.
Use a unique, randomly generated IV for each token. Never reuse an IV with the same key.
The payload and authentication tag are decrypted with AES-256-GCM.
From API7 Enterprise version 3.10.7, decryption is attempted first with the encoded protected header as AAD, then without it. Tokens from a standard JWE library and tokens carrying no AAD therefore both work. Where that is not available, the header is never passed as AAD, and a token generated with standard protected-header AAD fails 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"
}