Access token ngắn hạn (15m) + refresh token dài hạn (7d) là pattern chuẩn.
Flow: login → trả access + refresh token → access token hết hạn → dùng refresh token để lấy access token mới → rotate refresh token.
Refresh Token Rotation: mỗi lần refresh, invalidate token cũ và cấp token mới — detect token theft:
typescript
async refresh(refreshToken: string) {
const payload = this.jwtService.verify(refreshToken, { secret: REFRESH_SECRET });
const user = await this.usersService.findOne(payload.sub);
// Validate token khớp với stored (hashed) token
const isValid = await bcrypt.compare(refreshToken, user.hashedRefreshToken);
if (!isValid) throw new ForbiddenException('Token reuse detected');
// Rotate — tạo tokens mới, hash và lưu token mới
const tokens = await this.getTokens(user.id, user.email);
await this.updateRefreshToken(user.id, tokens.refreshToken);
return tokens;
}Revocation: lưu refresh token (bcrypt hash) trong DB, khi logout gọi updateRefreshToken(userId, null).
Pitfall: không lưu plain refresh token trong DB — luôn hash.