기존에 DB정보를 설정했던 .env 환경변수 설정 파일에 JWT의 SECRET 정보를 추가합니다.
Secret 정보와 JWT 정보의 만료 시간을 작성합니다.
...생략
JWT_SECRET=TestSecretKey
JWT_EXPIRATION_TIME=30
만료시간은 확인을 위해 30초로 설정합니다.
이전에 생성한 ormconfig.json파일과 constants.ts 파일은 삭제합니다.
이전에 Joi를 이용하여 환경변수를 검증했습니다. 똑같이 검증 절차를 걸치도록 AppModule에 추가합니다.
/src/app.module.ts
...생략
@Module({
imports: [
ConfigModule.forRoot({
validationSchema: Joi.object({
...생략
JWT_SECRET: Joi.string().required(),
JWT_EXPIRATION_TIME: Joi.string().required(),
}),
}),
UsersModule,
AuthModule,
DatabaseModule,
],
controllers: [AppController],
providers: [AppService, { provide: APP_GUARD, useClass: JwtAuthGuard }],
})
export class AppModule {}
auth module에는 환경 변수에서 가져와 검증까지 걸쳐 들어온 JWT_SECRET 값과 JWT_EXPIRATION_TIME 값을 넣어줘야합니다.
/src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategies/jwt.strategy';
import { LocalStrategy } from './strategies/local.strategy';
import { AuthController } from './auth.controller';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
UsersModule,
PassportModule,
ConfigModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
signOptions: {
expiresIn: `${configService.get('JWT_EXPIRATION_TIME')}s`,
},
}),
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService, JwtModule],
controllers: [AuthController],
})
export class AuthModule {}
사용자가 데이터를 요청할 때에는 쿠키에 존재하는 JWT의 Secret키와 비교를 위해 Config에서 Secret 값을 가져옵니다.
/src/auth/strategies/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UsersService } from '../../users/users.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService,
private readonly usersService: UsersService,
) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(request) => {
return request?.cookies?.Authentication;
},
]),
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload: any) {
return this.usersService.getById(payload.id);
}
}
/src/users/usersService.ts
...생략
@Injectable()
export class UsersService {
...생략
async getById(id: number) {
const user = await this.usersRepository.findOne({ id });
if (user) {
return user;
}
throw new HttpException(
'User with this id does not exist',
HttpStatus.NOT_FOUND,
);
}
}
/src/auth/auth.service.ts
...생략
@Injectable()
export class AuthService {
...생략
async login(user: User) {
const payload = { email: user.email, id: user.id };
const token = this.jwtService.sign(payload);
return token;
}
...생략
}
로그인 기능이 있으니 같은 방식으로 로그아웃 기능을 만들어 보려고 합니다. 로그아웃은 쿠키에 있는 Token값을 초기화 해주면 됩니다.
기존 로그인 기능의 쿠키 설정 부분이 Controller에 있었기 때문에 그부분은 Service로 이동시키도록하고 logout 메소드도 추가해보겠습니다.
/src/auth/auth.service.ts
...생략
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {}
...생략
async login(user: User) {
const payload = { email: user.email, id: user.id };
const token = this.jwtService.sign(payload);
return {
token: token,
domain: 'localhost',
path: '/',
httpOnly: true,
maxAge: Number(this.configService.get('JWT_EXPIRATION_TIME')) * 1000,
};
}
async logOut() {
return {
token: '',
domain: 'localhost',
path: '/',
httpOnly: true,
maxAge: 0,
};
}
...생략
}
기존 방식에서 약간의 변화를 주었습니다. 토큰만 반환 되던 방식이 아닌 토큰과 옵션 두가지가 포함됩니다. 반환된 결과는 token 부분과 Option 부분을 나누어 저장하여 사용합니다.
/src/auth/auth.controller.ts
...생략
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Public()
@UseGuards(LocalAuthGuard)
@Post('login')
async login(@Req() req, @Res({ passthrough: true }) res: Response) {
const { token, ...option } = await this.authService.login(req.user);
res.cookie('Authentication', token, option);
}
@Post('logout')
async logOut(@Res({ passthrough: true }) res: Response) {
const { token, ...option } = await this.authService.logOut();
res.cookie('Authentication', token, option);
}
...생략
}
성공적으로 완료 했다면 login 후 logout하면 cookie의 Token값이 빈공간으로 처리된다.