All articles
BackendMarch 28, 202610 min read
JWT Authentication in NestJS: Access Tokens, Refresh Tokens, and Rotation
A single JWT token is not enough for production. Here's how to implement the access token + refresh token pattern with rotation in NestJS.
NestJSJWTAuthenticationTypeScriptSecurity
JWT authentication with a single token works in development but creates a real security problem in production: when the token is stolen, you cannot revoke it until it expires. The access token + refresh token pattern solves this by separating concerns.
Why two tokens?
- Access token: short-lived (15 min), stateless. The server verifies the signature without a DB lookup — fast on every request.
- Refresh token: long-lived (7 days), stored in the database. Revocable at any time by deleting the DB record.
The complete flow
- User logs in → server returns access token + refresh token stored in DB.
- Client sends access token in the Authorization header on every API call.
- When access token expires, client sends refresh token to POST /auth/refresh.
- Server validates, deletes old token, issues new pair (rotation).
- On logout, server deletes the refresh token — session immediately invalidated.
Passport strategies in NestJS
// jwt.strategy.ts — access token
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get<string>('JWT_ACCESS_SECRET'),
});
}
validate(payload: JwtPayload): JwtPayload {
return payload;
}
}Token rotation on refresh
async refreshTokens(userId: string, refreshToken: string) {
const stored = await this.prisma.refreshToken.findUnique({
where: { token: refreshToken, userId },
});
if (!stored) throw new ForbiddenException('Invalid refresh token');
await this.prisma.refreshToken.delete({ where: { id: stored.id } });
return this.issueTokens(userId);
}Security checklist
- Use separate secrets for access and refresh tokens
- Set aggressive expiry on access tokens (15 min)
- On logout, always delete the refresh token from the DB
- For multi-device support, store one refresh token per device
NOTE
Storing the refresh token in an httpOnly cookie instead of in the Authorization header protects it from XSS attacks.
Back to all articles
Moses Kalunga