How to Rotate JWT Secrets Without Downtime
Key rotation is essential security hygiene, but doing it wrong causes authentication outages. Here's how to do it right.
The Problem
A naive rotation (swap old secret for new, restart) immediately invalidates all existing tokens, logging out every active user.
The Solution: Key ID (kid) Header
JWT supports a kid header claim that identifies which key was used to sign the token. By maintaining multiple active keys during a transition window, you achieve zero-downtime rotation.
Step 1: Generate New Key
const newSecret = crypto.randomBytes(32).toString('hex');
const newKeyId = 'key-2025-02';Step 2: Add to Key Store (Keep Old Key)
const keyStore = {
'key-2025-01': process.env.JWT_SECRET_OLD, // existing key
'key-2025-02': process.env.JWT_SECRET_NEW, // new key
};Step 3: Sign New Tokens with New Key
const token = jwt.sign(payload, keyStore['key-2025-02'], {
algorithm: 'HS256',
keyid: 'key-2025-02',
expiresIn: '15m',
});Step 4: Verify Using kid Header
function verifyToken(token) {
const decoded = jwt.decode(token, { complete: true });
const kid = decoded?.header?.kid;
const secret = keyStore[kid];
if (!secret) throw new Error('Unknown key ID');
return jwt.verify(token, secret, { algorithms: ['HS256'] });
}Step 5: Retire Old Key After Transition
Wait until all tokens signed with the old key have expired (based on your token TTL), then remove it from the key store and revoke the old secret.
Timeline
Day 0: Add new key, continue signing with old key
Day 1: Switch signing to new key (old key still in verifier)
Day 1 + token TTL: Remove old key from verifierThis approach ensures no active user gets logged out during rotation.