Validate JWKS rotation
Classify the rotation state between two JWKS snapshots.
POST /v1/validate/jwks-rotation Classify the rotation state between two JWKS snapshots.
Compares a previous_jwks snapshot with a current_jwks snapshot, classifies the rotation, and optionally verifies sample tokens against each side. Designed to run as a periodic check (cron, scheduled CI job) so rotations are caught before in-flight tokens start failing.
Request
| Field | Type | Required | Description |
|---|---|---|---|
| previous_jwks | JWKS | yes | Last-known JWKS document. Object with a keys array. |
| current_jwks | JWKS | yes | Current JWKS document. |
| overlap_policy | object | no | Operational thresholds. min_overlap_count: minimum number of shared kids required for safe_overlap. max_token_ttl_seconds: longest in-flight token lifetime; informs the recommended grace period. |
| sample_token | string | no | A token whose signature should verify against current_jwks. Use to spot-check that the new key set produces the expected key for the kid on a fresh token. |
| sample_old_token | string | no | A token issued under the previous key set. Should still verify under current_jwks during a safe overlap. |
| sample_new_token | string | no | A token signed with a key only in current_jwks. Confirms the new key is in service. |
Response — 200
| Field | Type | Required | Description |
|---|---|---|---|
| rotation_state | "no_change" | "safe_overlap" | "overlap" | "disjoint" | yes | See Rotation states below. |
| findings | Finding[] | yes | Operational concerns. Empty when state is no_change or safe_overlap within policy. |
| summary | string | yes | One-sentence description. |
Rotation states
| State | Meaning |
|---|---|
no_change | previous_jwks == current_jwks. No rotation in progress. |
safe_overlap | All previous keys still present in current_jwks, plus at least one new key. New tokens can be issued under the new key while old tokens continue to verify. The desired state during a rotation. |
overlap | Some, but not all, previous keys remain. Tokens issued under the dropped previous keys will fail verification. Only safe if you can guarantee no in-flight tokens reference the dropped keys (e.g. their kids have been retired for longer than max_token_ttl_seconds). |
disjoint | No keys in common. Every in-flight token issued under the previous set will fail. Roll back, or accept a verification outage during cutover. |
200 rotation in progress with safe overlap — recommended state
{
"rotation_state": "safe_overlap",
"findings": [
{
"code": "ROTATION_IN_PROGRESS",
"severity": "warning",
"message": "Rotation in progress: 1 new key added, all previous keys retained.",
"evidence": {
"shared_kids": [
"k1"
],
"new_kids": [
"k2"
],
"dropped_kids": []
}
}
],
"summary": "JWKS rotation state: safe overlap (in-progress rotation)."
} Disjoint state is an outage
A disjoint result with no rollback path means in-flight tokens will fail signature verification immediately. Do not advance rotations into disjoint; require safe_overlap for at least max_token_ttl_seconds first.
Errors
| Channel | Code | Cause |
|---|---|---|
| 200, in findings | NO_KEY_OVERLAP | Current set shares zero keys with previous. |
| 200, in findings | ROTATION_IN_PROGRESS | Safe overlap detected (informational). |
| 200, in findings | ROTATION_UNCLEAR | JWKS lacks the metadata (e.g. kid) needed to classify. |
| 422 | — | Missing previous_jwks or current_jwks. |
Example
curl -sSf -X POST "$JWTSHIELD_URL/v1/validate/jwks-rotation" \
-H "Authorization: Bearer $JWTSHIELD_KEY" \
-H "Content-Type: application/json" \
-d '{
"previous_jwks": { "keys": [{"kid":"k1","kty":"RSA","n":"...","e":"AQAB"}] },
"current_jwks": { "keys": [{"kid":"k1","kty":"RSA","n":"...","e":"AQAB"},
{"kid":"k2","kty":"RSA","n":"...","e":"AQAB"}] },
"overlap_policy": { "min_overlap_count": 1, "max_token_ttl_seconds": 86400 }
}'
See JWKS rotation guide for the operational pattern around this endpoint.