JWT Authentication in Production: Access Tokens, Refresh Token Rotation & Revocation
Architecting secure stateless authentication: token structure, HTTP-only cookie storage against XSS/CSRF, automated rotation family tracking, and Redis blacklisting.
By Amr Samir• August 19, 2026• 1 min
JWT Authentication in Production: Access Tokens, Refresh Token Rotation & Revocation
1. The Myth of "Completely Stateless" JWTs
JSON Web Tokens (JWT) are widely adopted for distributed authentication. However, naive implementations suffer from critical security flaws:
- No Instant Revocation: If a user's account is compromised, a valid stateless JWT cannot be revoked until its expiration time.
- XSS Vulnerabilities: Storing JWTs in browser
localStorageallows any malicious third-party script to steal user credentials.
code
+----------------------------------------------------------------------+
| JWT Security Token Architecture |
| |
| [ Access Token ] --> Short TTL (5-15 mins) --> Memory / HTTP-Only |
| [ Refresh Token ] --> Long TTL (7-30 days) --> Secure Cookie + DB |
+----------------------------------------------------------------------+
2. Refresh Token Rotation with Reuse Detection
code
1. Client sends Refresh Token #1 -> Server validates.
2. Server invalidates Refresh Token #1 and issues [ Access Token #2, Refresh Token #2 ].
3. If an attacker attempts to replay Refresh Token #1:
💥 Server detects token reuse, immediately REVOKES the entire token family,
and forces all sessions to re-authenticate!
3. Secure Storage: HTTP-Only Cookies vs LocalStorage
Always store tokens in HTTP-Only, Secure, SameSite=Strict/Lax Cookies:
- Inaccessible to client JavaScript (
document.cookie), completely mitigating XSS token extraction. - Protected against CSRF attacks when paired with
SameSitepolicy and anti-CSRF verification tokens.
4. Production NestJS Implementation
typescript
@Injectable()
export class AuthService {
async rotateRefreshToken(userId: string, oldToken: string) {
const tokenRecord = await this.prisma.refreshToken.findUnique({ where: { token: oldToken } });
if (!tokenRecord) {
// Possible compromise! Revoke all tokens for this user family
await this.prisma.refreshToken.deleteMany({ where: { userId } });
throw new UnauthorizedException('Security alert: Token reuse detected. Please log in again.');
}
if (tokenRecord.expiresAt < new Date()) {
await this.prisma.refreshToken.delete({ where: { id: tokenRecord.id } });
throw new UnauthorizedException('Session expired');
}
// Invalidate old token and issue new pair
await this.prisma.refreshToken.delete({ where: { id: tokenRecord.id } });
const newAccessToken = this.jwtService.sign({ sub: userId }, { expiresIn: '15m' });
const newRefreshToken = crypto.randomBytes(40).toString('hex');
await this.prisma.refreshToken.create({
data: {
token: newRefreshToken,
userId,
expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000),
},
});
return { accessToken: newAccessToken, refreshToken: newRefreshToken };
}
}
5. Summary Checklist
- Set Access Token expiration to 10–15 minutes maximum.
- Store Refresh Tokens in the database with family tracking for reuse detection.
- Deliver tokens exclusively via
httpOnly,secure, andsameSitecookies.
Recommended Posts
Related Projects
YallaBaytak - Multi-Role Real Estate Operating Platform
Real estate operating system featuring multi-role workflows, WhatsApp alerts, and geospatial mapping in Cairo & Giza.