필요한 함수
async getCookieWithJwtAccessToken(email: string) {
const payload = { email };
const token = this.jwtService.sign(payload, {
secret: this.configService.get('JWT_SECRET'),
expiresIn: `${this.configService.get(
'JWT_ACCESS_TOKEN_EXPIRATION_TIME',
)}s`,
});
return {
accessToken: token,
accessOption: {
domain: 'localhost',
path: '/',
httpOnly: true,
maxAge:
Number(this.configService.get('JWT_ACCESS_TOKEN_EXPIRATION_TIME')) *
1000,
},
};
}
async getCookieWithJwtRefreshToken(email: string) {
const payload = { email };
const token = this.jwtService.sign(payload, {
secret: this.configService.get('JWT_SECRET'),
expiresIn: `${this.configService.get(
'JWT_REFRESH_TOKEN_EXPIRATION_TIME', // 일주일
)}s`,
});
return {
refreshToken: token,
refreshOption: {
domain: 'localhost',
path: '/',
httpOnly: true,
maxAge:
Number(this.configService.get('JWT_REFRESH_TOKEN_EXPIRATION_TIME')) *
1000,
},
};
}
async updateRefreshTokenInUser(refreshToken: string, email: string) {
if (refreshToken) {
refreshToken = await bcrypt.hash(refreshToken, 10);
}
await this.userRepository.update(
{ email },
{
currentHashedRefreshToken: refreshToken,
},
);
}
async login({ email, password }: LoginInputDto) {
const user = await this.userRepository.findOne({ email });
if (!user) {
throw new UnauthorizedException('존재하지 않는 사용자입니다.');
}
const match = await bcrypt.compare(password, user.password);
if (!match) {
throw new UnauthorizedException('비밀번호가 틀립니다.');
}
//이메일 인증되지 않은 사용자 에러처리
if (!user.verified) {
throw new UnauthorizedException('이메일 인증을 해야합니다.');
}
const { accessToken, accessOption } =
await this.getCookieWithJwtAccessToken(email);
const { refreshToken, refreshOption } =
await this.getCookieWithJwtRefreshToken(email);
await this.updateRefreshTokenInUser(refreshToken, email);
const returnUser = await this.userRepository
.createQueryBuilder('user')
.select([
'user.id',
'user.username',
'user.email',
'user.ksDepartment',
'user.enterYear',
'user.verified',
])
.where('user.email = :email', { email })
.getOne();
return {
accessToken,
accessOption,
refreshToken,
refreshOption,
user: returnUser,
};
}
@Post('/login')
async login(
@Res({ passthrough: true }) res: Response,
@Body(ValidationPipe) loginInputDto: LoginInputDto,
) {
const { accessToken, accessOption, refreshToken, refreshOption, user } =
await this.authService.login(loginInputDto);
res.cookie('Authentication', accessToken, accessOption);
res.cookie('Refresh', refreshToken, refreshOption);
return { user };
}
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(UserRepository)
private readonly userRepository: UserRepository,
private readonly configService: ConfigService,
) {
super({
secretOrKey: configService.get<string>('JWT_SECRET'),
jwtFromRequest: ExtractJwt.fromExtractors([
(request) => {
return request?.cookies?.Authentication;
},
]),
});
}
async validate({ email }) {
const user: User = await this.userRepository.findOne(
{ email },
{ select: ['id', 'ksDepartment', 'verified', 'username', 'email'] },
);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
필요한 함수
async getUserRefreshTokenMatches(
refreshToken: string,
email: string,
): Promise<{ result: boolean }> {
const user = await this.userRepository.findOne({ email });
if (!user) {
throw new UnauthorizedException('존재하지 않는 사용자입니다.');
}
const isRefreshTokenMatch = await bcrypt.compare(
refreshToken,
user.currentHashedRefreshToken,
);
if (isRefreshTokenMatch) {
return { result: true };
} else {
throw new UnauthorizedException();
}
}
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(
Strategy,
'jwt-refresh-token',
) {
constructor(
@InjectRepository(UserRepository)
private readonly userRepository: UserRepository,
private readonly configService: ConfigService,
private readonly authService: AuthService,
) {
super({
secretOrKey: configService.get<string>('JWT_SECRET'),
jwtFromRequest: ExtractJwt.fromExtractors([
(request) => {
return request?.cookies?.Refresh;
},
]),
passReqToCallback: true,
});
}
async validate(req, { email }) {
const refreshToken = req.cookies?.Refresh;
console.log(refreshToken);
await this.authService.getUserRefreshTokenMatches(refreshToken, email);
const user: User = await this.userRepository.findOne(
{ email },
{ select: ['id', 'ksDepartment', 'verified', 'username', 'email'] },
);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtRefreshTokenGuard extends AuthGuard('jwt-refresh-token') {}
@Post('/refresh')
@UseGuards(JwtRefreshTokenGuard)
async refresh(
@Res({ passthrough: true }) res: Response,
@GetUser() user: User,
) {
this.logger.verbose(`User: ${user.username} trying to refreshToken`);
if (user) {
const { accessToken, accessOption } =
await this.authService.getCookieWithJwtAccessToken(user.email);
res.cookie('Authentication', accessToken, accessOption);
return { user };
}
}
필요한 함수
getCookiesForLogOut() {
return {
accessOption: {
domain: 'localhost',
path: '/',
httpOnly: true,
maxAge: 0,
},
refreshOption: {
domain: 'localhost',
path: '/',
httpOnly: true,
maxAge: 0,
},
};
}
logoutAPI
@Get('/logout')
@UseGuards(JwtRefreshTokenGuard)
async logOut(
@Res({ passthrough: true }) res: Response,
@GetUser() user: User,
) {
const { accessOption, refreshOption } =
this.authService.getCookiesForLogOut();
await this.authService.removeRefreshToken(user.email);
res.cookie('Authentication', '', accessOption);
res.cookie('Refresh', '', refreshOption);
}