Nest.js 위에서 GraphQL API를 위한 User 인증 JWT Token 생성하기

송인성·2021년 9월 12일
5
post-thumbnail

🧑🏻‍💻 해당 코드를 참고하기위해 참고한 공간 : https://docs.nestjs.com/security/authentication

백엔드 서버에서 로그인 인증 기능을 구현하기위해서 nestjs의 공식문서를 참고하여 jwt 기능을 사용하였습니다.
이번 포스팅은 제가 다시 nest.js의 jwt를 붙이려고 했을때 들어와서 보기위해 정리를 하는 내용입니다.

✅ 해당 글을 읽은 후 얻을 수 있는 지식

  • graphql resovler를 통하여 요청및 처리해야하는 API들 중에서 로그인한 유저의 정보가 있을 경우에만 해당 API를 처리해주고자 할때 유저를 확인하고 그 유저가 맞다면 API를 동작할 수 있게 하고자 하는 역할을 수행 할 수 있습니다.

nest.js를 통해서 API 처리를 graphql을 통해서 처리한다는 가정하에 작성합니다.

app.modules.ts

@Module({
  imports: [
    GraphQLModule.forRoot({ //graphql을 연결하기 위한 초기 설정 입니다.
      debug: true,
      playground: true,
      installSubscriptionHandlers: true,
      autoSchemaFile: 'schema.gql',
      context: ({ req, connection }) => { //graphql에게 request를 요청할때 req안으로 jwt토큰이 담깁니다.
        if (req) {
          //이부분이 처음 보시는 분들에게는 의아할 수 있습니다. graphql을 사용하면서 req.headers.authorization를 어떻게 담아서 보내는거지? 하실 수 있습니다. 해당 부분은 밑에 부분에서 설명하겠습니다.
          const user = req.headers.authorization;
          return { ...req, user };
        } else {
          return connection;
        }
      },
    }),
    AuthModule,
  ],
  providers: [GqlAuthGuard],
})

위에서 보았던 req.headers.authorization의 부분에 대해서 설명할 필요가 있습니다. 우리는 request header로 통신을 합니다. 따라서 유저의 인증을 하고자 할때 유저의 정보를 req.header에 넣어서 요청합니다 "내가 이 유저야 내 토큰은 이거야"라고 알려주는 역할을 합니다.

그렇다면 본론으로 돌아와서 graphql playground에서 어떻게 req.header에 유저 정보 토큰을 넣을 수 있을까요?


위의 사진과 같이 HTTP Headers라는 부분을 graphql playground는 제공합니다.
따라서 해당 부분에 jwt를 통해서 받은 token정보를 실어서 보내주면됩니다.
양식의 경우에는

{"Authorization":"Bearer 토큰정보"}

형식으로 담아 보냅니다.

{"Authorization":"토큰정보"} 👈 와 같이 했다가 삽질을 엄청했습니다.. 주의 하십쇼!

이렇게 하면 app.modules.ts에서 작성해야할 초기 세팅은 완료가 되었습니다.

이제 초기세팅 코드에서 보이는 코드중 크게 두개의 코드를 더 보아야합니다.
AuthModule, GqlAuthGuard 부분입니다. 차례대로 보겠습니다.

auth.module.ts

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserRepository } from 'src/user/user.repository';
import { AuthResolver } from './auth.resolver';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { jwtConstants } from './jwtContans';

@Module({
  imports: [
    //이번 기회를 통해서 얻은 부분중 하나입니다. jwt를 사용하려면 사용하려는 곳에 jwtModule을 import해줘야합니다.
    JwtModule.register({
      secret: jwtConstants.secret, //해당 부분은 jwtContants파일에 secret이라는 secret를 만들어서 넣어주었습니다 필수로 필요합니다! jwt는 크게 3가지 형식으로 이루어져있습니다. header,payload,signiture verify로 이루어져있는데 우리가 가지고자 하는 유저정보의 경우에는 payload에 들어가게되는데 해당 json정보를 암호화할 방식을 결정하는 장치가 됩니다.!
      signOptions: { expiresIn: '1 day' },// 해당부분은 토큰의 유지기간을 설정하는 부분입니다.
    }),
  ],
  providers: [AuthService, AuthResolver, JwtStrategy],//이부분들 역시도 인증과 관련된 부분을 구현하기위해서 필요한 파일입니다.
  exports: [],
})
export class AuthModule {}

위의 파일과 같이 진행을 완료했다면 이제
providers와 관련된 부분들을 살펴 보겠습니다.

JwtStrategy.ts부터 살펴보겠습니다.

해당 파일의 역할은 jwt를 통하여 req, res를 하고자할때 initialization하는 역할과 같다라는 느낌을 받았습니다.
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './jwtContans';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret,
    });
  }

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

JwtStrategy.ts의 파일구조는 위와 같습니다. 해당 PassportStrateg를 상속받은 JWTStrategy class를 만들어줌으로써 auth.module.ts의 class가 객체로 생성되어 메모리에 올려질때 jwt와 관련된 송수신 정보를 initialization해주는 코드로 저는 이해했습니다!

다음으로는,
AuthService.ts

import { Injectable, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { env } from 'process';
import { UserRepository } from 'src/user/user.repository';
import { jwtConstants } from './jwtContans';

@Injectable()
export class AuthService {
  private logger: Logger;
  constructor(
    @InjectRepository(UserRepository)
    private userRepository: UserRepository,
    private jwtService: JwtService,
  ) {
    this.logger = new Logger('AuthService');
  }

  async login(email: string, password: string): Promise<String> {
    const user = await this.userRepository.userFindOne(email, password);

    if (user) {
      const { ...result } = user;
      this.logger.log('validateUser Result: ', result);

      const payload = { username: user.email, sub: user.id };

      return this.jwtService.sign(payload);
    }
  }
}

AuthResolver.ts파일에서 작성할 API를 위한 메소드를 작성해놓은 service파일입니다.

AuthResolver.ts

import { Logger, UseGuards } from '@nestjs/common';
import {
  Resolver,
  Query,
  Mutation,
  Args,
  Context,
  GqlContextType,
} from '@nestjs/graphql';
import { UserEntity } from 'src/entities/user.entity';
import { GqlAuthGuard } from 'src/gql-auth-guard/gql-auth-guard.service';
import { UserObject } from 'src/user/dto/user.object';
import { AuthService } from './auth.service';
import { AuthDTO } from './dto/auth.dto';

export interface Context {
  user?: any;
}
@Resolver()
export class AuthResolver {
  private logger: Logger;
  constructor(private authService: AuthService) {
    this.logger = new Logger('AuthResolver');
  }

  @Query(() => String)
  async login(@Args('data') data: AuthDTO) {
    try {
      const { email, password } = data;
      const token = this.authService.login(email, password);

      return token;
    } catch (e) {
      this.logger.error(e);
      return false;
    }
  }
}

해당 resolver를 통해서 login을 합니다. login에 성공했을경우에는
jwt를 통한 token이 생성됩니다. 이제 여기서
생성된 token을 graphql playground의 http header에 넣어서
작동할 수 있도록 합니다.

이렇게 만든 jwt토큰을 어떻게 써야할까?
제가 만들고자 하는 프로젝트에서 post는 로그인을 한 사용자만 사용할 수 있도록 하는 프로젝트이기에 위의 설명들을 통해서 만든 jwt토큰을 이제 사용하여
유저인지 아닌지 확인하여 유저라면 해당 api를 작동시킬 수 있게 해준 코드입니다.

import { UseGuards } from '@nestjs/common';
import { Resolver, Query, Args, Mutation } from '@nestjs/graphql';
import { GqlAuthGuard } from 'src/gql-auth-guard/gql-auth-guard.service';
import { CreatePostInputDTO } from './dto/create-post-input.dto';
import { PostObject } from './dto/post.object';
import { PostService } from './post.service';

@Resolver()
export class PostResolver {
  constructor(private readonly postService: PostService) {}
  @UseGuards(GqlAuthGuard) //이부분이 가장 핵심이 되는 기능입니다. nestjs의 강력한 기능중 하나라고 생각합니다. 메소드를 따로 만들어서 유저가 보낸 jwt정보를 확인하지 않고 위에서 GqlAuthGuard만들어놓은 해당 파일을 불러서 써줌으로써 확인을 해줍니다. 너무 좋네요 :)
  @Mutation(() => Boolean)
  async createPost(@Args('data') data: CreatePostInputDTO): Promise<boolean> {
    const { title } = data;
    return await this.postService.createPost({ title });
  }
}

GqlAuthGuard파일이 어떻게 되어있는데! 라고 물을 수 있습니다. 해당 파일은

import { ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);

    return ctx.getContext().req;
  }
}

와 같이 생겼습니다 nest.js에서 제공하는 AuthGuard를 상속받아서
getRequest를 graphQL에 맞게 수정한후 사용합니다 :)

이상으로 내가 보기위한 Nest.js 위에서 GraphQL API를 위한 User 인증 JWT Token 생성하기 편을 마치겠습니다.

부족한 설명이 있었거나, 올바르지 않은 정보가 있었다면 언제든지 피드백 주세요 감사합니다 🧑🏻‍💻 모두 즐코하세요!

profile
코드 한줄에 의미를 생각할 수 있는 개발자가 되어 가는중... 🧑🏻‍💻

2개의 댓글

comment-user-thumbnail
2022년 1월 8일

감사합니다. 많은 도움 되었습니다 : )

1개의 답글