JWT Signing-Key Rotation¶
Sentinel signs all tokens (access / admin / refresh / authz) with an RSA private
key. Every issued token carries a kid (RFC-7638 thumbprint) header, and
/.well-known/jwks.json publishes the current key plus any retired keys so
verifiers can pick the right one during a rotation.
This makes rotation graceful: a new key can start signing while tokens from the previous key keep verifying until they expire — no outage.
Configuration¶
| Variable | Meaning |
|---|---|
JWT_PRIVATE_KEY_PATH |
Current signing key (default keys/private.pem). |
JWT_PUBLIC_KEY_PATH |
Current public key (default keys/public.pem). |
JWT_PREVIOUS_PUBLIC_KEY_PATHS |
Comma-separated retired public keys still inside their verification window. Empty by default. |
The current key's kid is derived from its public key, so it is stable across
restarts and identical between the signer and JWKS.
Planned rotation (zero downtime)¶
- Generate a new keypair:
- Retire the current public key — add its path to
JWT_PREVIOUS_PUBLIC_KEY_PATHS: - Promote the new key:
- Reload Sentinel. New tokens sign with the new
kid; tokens still bearing the oldkidcontinue to verify because the old public key is inJWT_PREVIOUS_PUBLIC_KEY_PATHSand is published in JWKS. - Wait out the overlap. Keep the old public key for at least
REFRESH_TOKEN_EXPIRE_DAYS(default 7 days) — the longest-lived token. After that, remove it fromJWT_PREVIOUS_PUBLIC_KEY_PATHSand reload. Old tokens are then rejected (theirkidis no longer in the verify set).
Client pickup: SDK-protected services fetch JWKS and select the verifying key by
kid, refetching JWKS automatically when they see an unknownkid. No client restart is required for a planned rotation.
Emergency rotation (key compromise)¶
If the private key may be compromised, do not keep it in the verify set:
- Generate and promote a new key (steps 1, 3 above).
- Do not add the compromised public key to
JWT_PREVIOUS_PUBLIC_KEY_PATHS— any token signed by the leaked key must stop verifying immediately. - Reload. All tokens signed by the old key are now rejected.
- Force re-authentication: flush refresh-token families / blacklist outstanding
jtis as needed (seedocs/security.md→ Token Lifecycle).
Client upgrade note (for existing dev clients)¶
The two current dev client apps must run an SDK version that resolves the
verifying key by kid (keyset + refetch-on-unknown-kid). Older SDK builds
pinned a single key and would break on rotation.
Before exercising rotation:
1. Bump both client apps to the current SDK and redeploy.
2. Because verification is strict-kid, any tokens those clients still hold from
before the server upgrade are rejected once — users re-authenticate.
There is no need to support old-SDK clients during a transition while all clients are in dev and upgraded together. Revisit this note if any client ships to production before rotation is exercised.
Verifying¶
curl https://<base_url>/.well-known/jwks.jsonlists one key normally, and two during the overlap window — each with a distinctkid.- A freshly minted token's header
kid(decode the JWT header) matches a key in JWKS.