Add JWT_SECRET_NEXT env var support for seamless JWT secret rotation: - JwtStrategy: use secretOrKeyProvider to try primary then fallback key - TokenService.verifyAccessToken(): dual-key fallback for internal callers - Redis metric jwt_verify_with_next_total for monitoring cut-over progress - Session revocation marker support restored in JwtStrategy.validate() - Unit tests for all three verification scenarios (primary, fallback, both-fail) - docs/security/secret-rotation.md runbook with step-by-step rotation procedure Closes GOO-203. Co-Authored-By: Paperclip <noreply@paperclip.ing>
34 lines
1.1 KiB
Markdown
34 lines
1.1 KiB
Markdown
# JWT Secret Rotation Runbook
|
|
|
|
Zero-downtime JWT secret rotation using dual-key verification.
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Required | Description |
|
|
|----------|----------|-------------|
|
|
| `JWT_SECRET` | Yes | Primary signing and verification key |
|
|
| `JWT_SECRET_NEXT` | No | Fallback verification-only key during rotation |
|
|
|
|
## How It Works
|
|
|
|
- **Signing**: Always uses `JWT_SECRET` (primary). Tokens are never signed with `_NEXT`.
|
|
- **Verification**: Tries `JWT_SECRET` first. On failure, falls back to `JWT_SECRET_NEXT` if set.
|
|
- **Metric**: Each fallback verification increments `metrics:jwt_verify_with_next_total` in Redis.
|
|
|
|
## Rotation Procedure
|
|
|
|
1. Generate new secret: `openssl rand -base64 48`
|
|
2. Deploy with `JWT_SECRET=<old>`, `JWT_SECRET_NEXT=<new>`
|
|
3. Swap: `JWT_SECRET=<new>`, `JWT_SECRET_NEXT=<old>`
|
|
4. Wait 15 minutes (access token TTL)
|
|
5. Drop `JWT_SECRET_NEXT`
|
|
6. Verify no 401 spikes
|
|
|
|
## Rollback
|
|
|
|
Swap back: `JWT_SECRET=<old>`, `JWT_SECRET_NEXT=<new>`.
|
|
|
|
## Emergency: Forced Re-login
|
|
|
|
Rotate to a new secret without setting `_NEXT`. All existing tokens become invalid.
|