Node.js › Authentication & Authorization
How do you implement JWT refresh token rotation in a Node.js API, and why is it more secure than a single long-lived token?
AdvancedAnswer
A single long-lived JWT, if stolen, remains valid and dangerous for its entire lifetime. Refresh token rotation instead issues a short-lived access token (minutes) alongside a longer-lived refresh token (days/weeks) stored securely (often an httpOnly cookie); when the access token expires, the client uses the refresh token to obtain a new access token AND a new refresh token, invalidating the old refresh token -- if a stolen refresh token is ever used after the legitimate client has already rotated it, the reuse can be detected and the entire token family revoked.
app.post('/refresh', async (req, res) => {
const oldRefreshToken = req.cookies.refreshToken;
const stored = await refreshTokenStore.find(oldRefreshToken);
if (!stored || stored.used) {
await refreshTokenStore.revokeFamily(stored?.familyId); // detected reuse: revoke everything
return res.status(401).json({ error: 'Token reuse detected' });
}
await refreshTokenStore.markUsed(oldRefreshToken);
const newAccessToken = jwt.sign({ userId: stored.userId }, JWT_SECRET, { expiresIn: '15m' });
const newRefreshToken = await refreshTokenStore.create(stored.userId, stored.familyId);
res.cookie('refreshToken', newRefreshToken, { httpOnly: true, secure: true }).json({ accessToken: newAccessToken });
});Real-world example A banking API implements refresh token rotation with reuse detection, so that if an attacker manages to steal a refresh token and use it after the legitimate user's client has already rotated past it, the system automatically revokes every token in that session's family, immediately locking out the attacker's stolen credential.