Authentication and AuthorizationLesson 4.3
Refresh tokens — implementing secure token rotation
access token vs refresh token, token rotation, httpOnly cookies, refresh endpoint, token families, revocation, sliding sessions
Access Tokens and Refresh Tokens
Short-lived access tokens minimize damage from token theft. Refresh tokens extend sessions without requiring re-login, but require server-side storage for revocation.
Issuing Both Tokens at Login
const issueTokens = async (res, user) => {
const accessToken = jwt.sign(
{ sub: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ sub: user.id },
process.env.REFRESH_SECRET,
{ expiresIn: '7d' }
);
// Store refresh token in DB for revocation
await RefreshToken.create({ token: refreshToken, userId: user.id });
// Send refresh token in httpOnly cookie — not accessible by JavaScript
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'Strict',
maxAge: 7 * 24 * 60 * 60 * 1000
});
return accessToken; // Send access token in body
};Refresh Endpoint
app.post('/auth/refresh', async (req, res) => {
const token = req.cookies.refreshToken;
if (!token) return res.status(401).json({ error: 'No refresh token' });
const stored = await RefreshToken.findOne({ token });
if (!stored) return res.status(403).json({ error: 'Token reuse detected' });
await stored.delete(); // Rotate — invalidate old token
const decoded = jwt.verify(token, process.env.REFRESH_SECRET);
const user = await User.findById(decoded.sub);
const accessToken = await issueTokens(res, user); // Issue new pair
res.json({ accessToken });
});