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
import { createHash } from 'node:crypto';
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
// bcrypt chỉ băm 72 byte đầu — mọi JWT của cùng user giống hệt nhau ở 72 byte đó,
// nên phải SHA-256 trước khi bcrypt (cả lúc lưu lẫn lúc so) thì mới detect được reuse
const tokenDigest = createHash('sha256').update(refreshToken).digest('hex');
const isValid = await bcrypt.compare(tokenDigest, 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 bcrypt(SHA-256(refresh token)) trong DB, khi logout gọi updateRefreshToken(userId, null).
Lưu ý: không lưu plain refresh token trong DB — luôn hash.