Prefer to watch?
This article has a full video walkthrough on YouTube
Same content, live coding, 24 min — no fluff.
The problem with standard JWT authentication
JSON Web Tokens (JWT) are stateless and simple, but they pose a security challenge: if a token has a long expiration time and gets stolen, an attacker can access the system until the token expires. If it has a short expiration time, users are forced to log in repeatedly.
Refresh Token Rotation to the rescue
To solve this, we use short-lived access tokens (e.g., 15 minutes) and a long-lived refresh token stored in a secure, httpOnly cookie. Whenever the access token expires, a request is made to rotate the refresh token and generate a new access token.
// Express handler for token rotation
app.post('/refresh-token', async (req, res) => {
const { refreshToken } = req.cookies;
if (!refreshToken) return res.sendStatus(401);
// Find token, verify, check if reused (potential theft)
const user = await verifyRefreshToken(refreshToken);
if (!user) return res.sendStatus(403);
const newAccessToken = generateAccessToken(user);
const newRefreshToken = generateRefreshToken(user);
// Rotate and save
await updateRefreshTokenFamily(refreshToken, newRefreshToken);
res.cookie('refreshToken', newRefreshToken, { httpOnly: true, secure: true });
res.json({ accessToken: newAccessToken });
});
Handling token reuse detection
If a refresh token is reused, we must immediately invalidate the entire family of refresh tokens associated with that user. This protects users in case an attacker steals a refresh token, because as soon as either party tries to use it again, access is revoked.