Refresh JWT Auth Credentials
Before 3.9.19, the Control Plane added a fixed placeholder private_key field to every jwt-auth consumer credential that uses an asymmetric algorithm (RS*, ES*, PS*, or EdDSA) before writing it to the configuration store. The field existed only to satisfy the jwt-auth consumer schema of Data Planes older than 3.9.4, which rejected such a credential unless private_key was present. It was never a usable key, and the Data Plane never signed anything with it: verification uses public_key.
Data Planes 3.9.4 and later removed private_key from that schema, so the injected field became a field no schema declares. The Data Plane reports it in the configuration compatibility report:
plugin [jwt-auth] has unrecognized fields: private_key
The report entry is a warning: the credential is still applied and JWT verification keeps working. From 3.9.19, the Control Plane no longer adds the field, and this page describes how to clear the warning from configurations that already carry it.
Who Is Affected
You are affected if a consumer credential configures jwt-auth with an algorithm other than HS256, HS384, or HS512. Credentials that use an HS* algorithm never received the field.
The Control Plane does not remove the field from configurations that were already written. Upgrading alone therefore does not clear the warning — each affected credential has to be written once more, which the refresh script does.
Upgrade Steps
- Upgrade the Control Plane. See In-Place Upgrade.
- Upgrade every Data Plane. See Rolling Upgrade.
- Refresh the affected credentials with the script below.
Refresh only after every Data Plane has been upgraded. A Data Plane older than 3.9.4 still requires private_key: refreshing a credential while one is connected writes a configuration its consumer schema rejects, the Data Plane drops the credential, and requests authenticated by it start failing with 401.
Refresh the Affected Credentials
If only a few credentials are affected, do it from the Dashboard: open each one and save it without changing anything. Saving rewrites the credential, which is all the refresh needs. The script below does the same thing across every gateway group at once.
The script rewrites each affected credential through the Admin API with its current configuration, which makes the Control Plane sync it to the configuration store again without the placeholder field. It does not change any credential value.
It needs curl and jq, the Control Plane address, and a Dashboard token with permission to read and update consumer credentials. See Obtain a Token from the Dashboard.
export CP_ADDR="https://127.0.0.1:7443"
export API_KEY="a7ee-xxxxxxxxxxxxx"
#!/usr/bin/env bash
set -euo pipefail
api() {
curl -sk --fail-with-body -H "X-API-KEY: ${API_KEY}" -H "Content-Type: application/json" "$@"
}
for gg in $(api "${CP_ADDR}/api/gateway_groups?page_size=1000" | jq -r '.list[] | select(.type != "api7_ingress_controller") | .id'); do
for username in $(api "${CP_ADDR}/apisix/admin/consumers?gateway_group_id=${gg}&page_size=1000" | jq -r '.list[].username'); do
credentials=$(api "${CP_ADDR}/apisix/admin/consumers/${username}/credentials?gateway_group_id=${gg}&plugin_name=jwt-auth&page_size=1000")
while read -r credential; do
[ -n "${credential}" ] || continue
id=$(jq -r '.id' <<<"${credential}")
body=$(jq -c '{name, desc, labels, plugins} | with_entries(select(.value != null))' <<<"${credential}")
api -X PUT -d "${body}" \
"${CP_ADDR}/apisix/admin/consumers/${username}/credentials/${id}?gateway_group_id=${gg}" >/dev/null
echo "refreshed ${gg}/${username}/${id}"
done < <(jq -c '.list[] | select((.plugins["jwt-auth"].algorithm // "HS256") | startswith("HS") | not)' <<<"${credentials}")
done
done
If a gateway group holds more than 1000 consumers or a consumer more than 1000 credentials, raise page_size or page through the results with the page parameter. The total field of each list response tells you how many records exist.
The script skips gateway groups managed by the Ingress Controller: the Admin API does not accept writes to them from a Dashboard token, and their configuration is rewritten by the Ingress Controller on its next synchronization.
Consumers that carry jwt-auth in their own plugins field instead of in a credential predate credentials and cannot be rewritten through the Admin API, which rejects authentication plugins configured directly on a consumer. Move such a configuration to a credential to clear its warning.
Verify
In the Dashboard, open the gateway group, select a gateway instance, and check its configuration compatibility report. jwt-auth should no longer be listed with an unrecognized private_key field.
The report is rebuilt from the configuration each Data Plane holds, and the Data Plane sends it with its heartbeat, so allow one heartbeat interval after the refresh before reading it.