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

yiyb0603·2021년 3월 3일
3

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
안녕하세요

0개의 댓글