NestJs JWT Login 적용기-2

김예찬·2022년 11월 23일
0

JWT Strategy 적용

JWT 인증을 위한 패키지 설치

$ npm install --save @nestjs/jwt passport-jwt
$ npm install --save-dev @types/passport-jwt
  • @nestjs에서 passport 사용할 수 있게 해주는 패키지
  • passport-jwt : jwt 전략을 실행하기 위한 passport 패키지
  • @types/passport-jwt : jwt 전략을 구현할 때 타입스크립트 코드 작성을 도와줌

AuthService Login 메소드 작성

auth/auth.service.ts

  • jwtService 인스턴스 AuthService에 생성자를 통해 주입
  • local 전략을 통해 반환된 유저 정보를 토대로 payload를 구성
  • jwtSerivce sign 메소드를 통해 액세스 토큰 생성 및 반환
import { Injectable } from '@nestjs/common';
import { UserService } from 'src/user/user.service';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';

@Injectable()
export class AuthService {
  constructor(
    private userService: UserService,
		// jwtService 주입
    private jwtService: JwtService,
  ) {}

  async validateUser(email: string, password: string): Promise<any> {
    const user = await this.userService.findOneByEmail(email);
    const validatePassword = await bcrypt.compare(password, user.password);
    if (user && validatePassword) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }
// login 메소드 작성
  async login(user: any) {
    const payload = { username: user.name, sub: user.id };
    return {
			// 엑세스 토큰 생성 후 반환
      access_token: this.jwtService.sign(payload),
    };
  }
}

AuthModule에 JwtModule 추가

auth/constants.ts

  • secretKey 파일 생성
// 공식 문서 (실제 개발 시 환경 변수나 configuration service로 보호해야 함)
export const jwtConstants = {
  secret: 'secretKey',
};

auth/auth.module.ts

  • JwtModule register 메소드를 통해 secret key와 토큰 만료 시간 등록
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { UserModule } from '../user/user.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';

@Module({
  imports: [
    UserModule,
    PassportModule,
    JwtModule.register({
			// secret key 등록
      secret: jwtConstants.secret,
			// 토큰 만료 시간 설정
      signOptions: { expiresIn: '1d' },
    }),
  ],
  providers: [AuthService, LocalStrategy],
  exports: [AuthService],
})
export class AuthModule {}

Login route가 access token을 반환하도록 업데이트

app.controller.ts

import { Controller, Request, Get, Post, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthService } from './auth/auth.service';
import { LocalAuthGuard } from './auth/local-auth.guard';
@Controller()
export class AppController {
  constructor(private authService: AuthService) {}
// AuthGuard('local')을 상속하는 LocalAuthGuard 클래스
  @UseGuards(LocalAuthGuard)
  @Post('auth/login')
  async login(@Request() req) {
// req.user를 authService의 로그인 메소드에 인수로 넘겨주고 엑세스 토큰을 받는다.
    return this.authService.login(req.user);
  }
}

jwt.strategy.ts 파일 생성

auth/jwt.strategy.ts

  • 클라이언트가 토큰을 보냈을 때 토큰 검증하는 로직을 수행
  • 검증을 성공하면 페이로드의 유저 아이디와 유저이름을 req.user에 담아 return
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      //Request에서 jwt 토큰을 추출하는 방법을 설정 : Authorization에서 bearer token에 jwt 토큰을 담아 전송해야 함
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      //true로 설정하면 passport에 토큰 검증을 위임하지 않고 직접 검증
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret,
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

AuthModule에 JwtStrategy 추가

auth/auth.module.ts

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { UserModule } from '../user/user.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';

@Module({
  imports: [
    UserModule,
    PassportModule,
    JwtModule.register({
      secret: jwtConstants.secret,
      signOptions: { expiresIn: '1d' },
    }),
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}

JWT 전략 실행

JwtAuthGuard 클래스 생성

auth/jwt-auth.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

유저 인증이 필요한 API에 사용

user.controller.ts

@UseGuards(JwtAuthGuard)
  @Get('/:id')
  getUser(@Param('id') id: number) {
    return this.userService.findOne(id);
  }

  @UseGuards(JwtAuthGuard)
  @Patch()
  updateUser(@Request() req, @Body() data: UpdateUserDto) {
    return this.userService.update(req.user.userId, data);
  }

  @UseGuards(JwtAuthGuard)
  @Delete()
  deleteUser(@Request() req) {
    return this.userService.remove(req.user.userId);
  }
}
profile
이 밤 등불의 친구

0개의 댓글