Nest.js에서 jwt 토큰 관리하기 🎁

yiyb0603·2021년 3월 3일
14

Nest.js

목록 보기
1/4
post-thumbnail

안녕하세요! 오늘은 Nest.js에서 jwt 토큰 관리하기를 주제로 글을 작성해보도록 하겠습니다. 지금까지 프론트엔드 리액트를 주제로만 글을 쓰다가 오늘 처음 백엔드를 주제로 글을 작성해보는 것 같습니다.

1. 배경 🧮

기존에 제가 Express.js에서 API를 개발할때, Auth Middleware를 이용하여 쉽게 토큰을 관리하였으며, 인터넷이나 친구들의 예제를 통해서 쉽게 익힐 수 있었습니다.

하지만 Nest.js에서는 토큰을 관리하는 법에 대한 예제들이 많지 않았었고, 거의 다 영어로 되어있어서 무슨말인지 이해를 못할때가 많았습니다.

그러다가 저는 토큰 관리하는법을 유튜브에서 찾아보게 되었는데, 한 영상에서 좋은 예제를 찾게되었고, 쉽게 접근 할 수 있어서 아래의 글을 작성하게 되었습니다.

2. 클래스 코드 작성 🎁

클래스를 만들때, 첫번째로 Nest.js의 인터페이스인 CanActivate를 구현할것입니다. CanActivate는 토큰이 있는지 / 없는지를 판단하여 그다음 행동에 대한 제어권을 얻게 됩니다.

저는 src/middleware 폴더를 만든다음, auth.ts라는 파일을 만들어서 아래와 같이 코드를 작성해주었습니다.

import { CanActivate, ExecutionContext } from "@nestjs/common";
import HttpError from "exception/HttpError";
import { verifyToken } from "lib/token";
import getProcessEnv from 'lib/getProcessEnv';

export default class AuthGuard implements CanActivate {
  public canActivate(context: ExecutionContext): boolean {
    // CanActivate를 implements 하였으므로, canActivate 함수를 구현해야 합니다.
    const request = context.switchToHttp().getRequest();
    // 클라이언트에서 보낸 request 정보를 읽어옵니다.

    const { access_token } = request.headers;
    // 사용자가 헤더에 보낸 access_token key값의 토큰값.

    if (access_token === undefined) {
      // 토큰이 전송되지 않았다면
      throw new HttpError(401, '토큰이 전송되지 않았습니다.');
    }
    
    request.user = this.validateToken(access_token);
    // request.user 객체에 디코딩된 토큰(유저 정보)을 저장합니다.
    return true;
  }

  public validateToken(token: string): string {
    try {
      const verify: string = jwt.verify(token, getProcessEnv('JWT_SECRET')) as string;
      return verify;
    } catch (error) {
      switch (error.message) {
        // 토큰에 대한 오류를 판단합니다.
        case 'INVALID_TOKEN':
        case 'TOKEN_IS_ARRAY':
        case 'NO_USER':
          throw new HttpError(401, '유효하지 않은 토큰입니다.');

        case 'EXPIRED_TOKEN':
          throw new HttpError(410, '토큰이 만료되었습니다.');
        
        default:
          throw new HttpError(500, '서버 오류입니다.');
      }
    }
  }
}

3. Custom Decorator 제작 🎁

Controller에서 AuthGuard 클래스에서 선언해주었던 request.user 객체의 정보를 읽어오려고 할때, 커스텀 데코레이터를 제작하여 유저의 정보를 읽어오도록 해보겠습니다.

저는 src/lib 폴더 경로에 user.decorator.ts 파일을 만들어서 아래의 코드를 작성했습니다.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const Token = createParamDecorator((data, ctx: ExecutionContext): ParameterDecorator => {
  const request = ctx.switchToHttp().getRequest();
  // 클라이언트에서 보낸 request의 정보를 가져옵니다.
  
  // 이전에 AuthGuard 클래스에서 할당했던 request.user 객체의 정보를 return 해줍니다.
  return request.user;
});

위의 createParamDecorator 함수를 이용하여 커스텀 데코레이터를 제작 할 수 있습니다.
이제 위에서 만들었던 데코레이터를 이용하여 Controller에 적용해보도록 하겠습니다.

4. 적용하기 💽

예를 들어, 게시판 프로젝트를 만든다고 가정을 했을때 글 작성 API를 설계한다면 작성자의 정보를 데이터베이스에 적용을 해야합니다. 이때 작성자 정보는 주로 헤더로 보낸 토큰을 이용하여 주로 관리를 합니다. 그래서 글 작성 API의 컨트롤러에 데코레이터를 적용하여 테스트 해보도록 하겠습니다.

기존에 작성해놓은 Controller 코드가 있다면, 아래와 같이 수정해주세요!

import { Body, Post, Res, UseGuards } from "@nestjs/common";

@Post('/')
@UseGuards(new AuthGuard())
// useGuards 데코레이터를 이용하여 토큰 요청이 필요한 API임을 알립니다.
// UseGuards 안에는 2번 제목에서 선언했던 AuthGuard 클래스를 넣어줍니다.

public async handleCreatePost(
  @Res() response: Response,
  @Token() user: User,
  @Body() createPostDto: PostDto
) {
  console.log(user);
  await this.postService.handleCreatePost(createPostDto, user);

  return response.status(200).json({
    status: 200,
    message: '글 작성을 성공하였습니다.',
  });
}

해당 데코레이터를 불러와서 console.log(user)를 실행하여 토큰이 올바르게 전송되었는지 로그를 찍어보겠습니다. 콘솔에는 아래와 같이 잘 출력되었습니다.

이제 받아온 토큰 정보를 바탕으로 Service 파일에도 간단하게 적용해보겠습니다. 저는 TypeORM을 이용하여 데이터베이스와 연결을 해주었습니다.

public async handleCreatePost(createPostDto: PostDto, user: User): Promise<void> {
  const { introduction, thumbnail,title, contents, category, postTags } = createPostDto;
  const existUser = await this.userRepository.getUserById(user.githubId);
  // 토큰에서 받은 githubId 객체를 이용하여 유저를 찾습니다.

  const post: PostEntity = new PostEntity();
  post.user = existUser;
  // 유저 정보 삽입  

  post.title = title;
  post.introduction = introduction;
  post.thumbnail = thumbnail;
  post.contents = contents;
  post.category = category;

  await this.postRepository.save(post);
}

위의 코드를 다 작성하고나서, 데이터베이스를 확인해보면 올바르게 유저 값이 들어가있는 모습이 보일 것 입니다.

5. 글을 마치며 💽

사실 Nest.js에서의 토큰 관리는 제가 Nest를 사용해오면서 가장 알고싶었던 부분 중 하나였습니다. 그러다가 우연히 유튜브를 통해서 좋은 예제를 얻게되어서 앞으로의 토큰 관리를 쉽게 할 수 있을 것 같습니다.

이상으로 글을 마치도록 하겠습니다. 궁금한점이 있으시다면 댓글로 남겨주세요! 긴 글 읽어주셔서 감사합니다.

profile
블로그 이전: https://yiyb-blog.vercel.app

4개의 댓글

comment-user-thumbnail
2021년 8월 19일

혹시 보셨다는 유튜브가 어떤 영상인지 알수있을까요??

1개의 답글
comment-user-thumbnail
2021년 10월 3일

혹시 토큰 검증을 하는 부분에서 JwtService를 사용하고 싶은데 방법이 있을까요?
constructor의 JwtService를 인자 값으로 전달하니 new AuthGuard()에서 매개변수로 jwtService를 넘겨줘야 하는 이슈가 생겨서 여쭤봅니다ㅜ

1개의 답글

관련 채용 정보