feat: blacklist refresh tokens

This commit is contained in:
Abdalhamid Alhamad
2025-04-09 16:20:29 +03:00
parent 4c6ef17525
commit cbade0a87d
2 changed files with 25 additions and 4 deletions

View File

@ -295,6 +295,14 @@ export class AuthService {
async refreshToken(refreshToken: string): Promise<[ILoginResponse, User]> { async refreshToken(refreshToken: string): Promise<[ILoginResponse, User]> {
this.logger.log('Refreshing token'); this.logger.log('Refreshing token');
const isBlackListed = await this.cacheService.get(refreshToken);
if (isBlackListed) {
this.logger.error('Refresh token is blacklisted');
throw new BadRequestException('AUTH.INVALID_REFRESH_TOKEN');
}
try { try {
const isValid = await this.jwtService.verifyAsync<IJwtPayload>(refreshToken, { const isValid = await this.jwtService.verifyAsync<IJwtPayload>(refreshToken, {
secret: this.configService.getOrThrow('JWT_REFRESH_TOKEN_SECRET'), secret: this.configService.getOrThrow('JWT_REFRESH_TOKEN_SECRET'),
@ -306,6 +314,12 @@ export class AuthService {
const tokens = await this.generateAuthToken(user); const tokens = await this.generateAuthToken(user);
this.logger.log(`Blacklisting old tokens for user with id ${isValid.sub}`);
const refreshTokenExpiry = this.jwtService.decode(refreshToken).exp - Date.now() / ONE_THOUSAND;
await this.cacheService.set(refreshToken, 'BLACKLISTED', refreshTokenExpiry);
this.logger.log(`Token refreshed successfully for user with id ${isValid.sub}`); this.logger.log(`Token refreshed successfully for user with id ${isValid.sub}`);
return [tokens, user]; return [tokens, user];
@ -352,7 +366,7 @@ export class AuthService {
this.logger.log('Logging out'); this.logger.log('Logging out');
const accessToken = req.headers.authorization?.split(' ')[1] as string; const accessToken = req.headers.authorization?.split(' ')[1] as string;
const expiryInTtl = this.jwtService.decode(accessToken).exp - Date.now() / ONE_THOUSAND; const expiryInTtl = this.jwtService.decode(accessToken).exp - Date.now() / ONE_THOUSAND;
return this.cacheService.set(accessToken, 'LOGOUT', expiryInTtl); return this.cacheService.set(accessToken, 'BLACKLISTED', expiryInTtl);
} }
private async loginWithPassword(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> { private async loginWithPassword(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> {

View File

@ -1,12 +1,13 @@
import { Injectable } from '@nestjs/common'; import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt'; import { ExtractJwt, Strategy } from 'passport-jwt';
import { UserService } from '~/user/services';
import { IJwtPayload } from '../interfaces'; import { IJwtPayload } from '../interfaces';
@Injectable() @Injectable()
export class AccessTokenStrategy extends PassportStrategy(Strategy, 'access-token') { export class AccessTokenStrategy extends PassportStrategy(Strategy, 'access-token') {
constructor(configService: ConfigService) { constructor(configService: ConfigService, private userService: UserService) {
super({ super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false, ignoreExpiration: false,
@ -14,7 +15,13 @@ export class AccessTokenStrategy extends PassportStrategy(Strategy, 'access-toke
}); });
} }
validate(payload: IJwtPayload) { async validate(payload: IJwtPayload) {
const user = await this.userService.findUser({ id: payload.sub });
if (!user) {
throw new UnauthorizedException();
}
return payload; return payload;
} }
} }