백앤드(Back-End)/Nest.js

[Nest.js] Nest.js API 만들기 (8) - Authentication(Passport, Passport-Local) & Guards 로그인

RyanSin 2021. 11. 19. 02:14
반응형

- 개요

안녕하세요. 이번 시간에는 Nest.js에서 로그인 기능을 구현해보는 시간을 가져보겠습니다.

 

Nest.js 공식 홈페이지에서는 Passport를 권장하고 있습니다.

 

공식 홈페이지: https://docs.nestjs.com/security/authentication

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

 

인증 방식으로 passport-local 방식과 passport-jwt 방식을 소개하고 있습니다. 이번 시간에는 passport-local 방식을 사용해서 로그인 인증을 진행하겠습니다.

 

- 설정

우리는 몇 가지 모듈을 설치해야 합니다. 아래 모듈을 설치 해주세요.

 

#npm
npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local

#yarn
yarn add @nestjs/passport passport passport-local
yarn add @types/passport-local --dev

 

모듈을 설치했다면 auth module과 auth service를  만들어줍니다.

 

#module 생성
nest g module auth --no-spec

#service 생성
nest g service auth --no-spec

 

 

auth.module & auth.service 생성

 

다음으로는 auth 폴더 안에 guards 폴더와 strategies 폴더를 생성합니다.

 

- local-auth.guard.ts

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

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

 

- auth.local.strategy.ts

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from '../auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    // usernaem 키 이름 변경 user_id로 요청
    super({
      usernameField: 'user_id',
    });
  }

  async validate(user_id: string, password: string): Promise<any> {
    console.log('LocalStrategy');

    const user = await this.authService.validateUser(user_id, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

auth.local.strategy.ts 파일에서 중요한 부분은 super();를 통해 부모(PassportStrategy) 속성을 변경해 HTTP 요청 시 username 키 값이 아닌 user_id 키 값으로 요청이 가능하게 변경을 해줘야 합니다.

 

그리고 validate라는 이름을 꼭 사용해야 합니다. 만약 이름을 변경하고 사용하면 아래와 같은 에러가 발생합니다.

 

validate 메서드명을 사용하지 않고 다른 메소드 이름을 사용했기 때문에 찾을 수 없다는 Error입니다.

 

- auth.module.ts

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './strategies/auth.local.strategy';
import { AuthService } from './auth.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserRepository } from 'src/repository/user.repository';

@Module({
  imports: [TypeOrmModule.forFeature([UserRepository]), PassportModule],
  providers: [AuthService, LocalStrategy],
})
export class AuthModule {}

 

우리가 생성한 LocalStrategy와 PassportModule을 등록해줍니다. 그리고 UserPepository를 등록합니다.

 

- auth.service.ts 

import { Injectable } from '@nestjs/common';
import { UserRepository } from 'src/repository/user.repository';

@Injectable()
export class AuthService {
  constructor(private userRepository: UserRepository) {}

  /**
   * @author Ryan
   * @description 단일 유저 조회
   *
   * @param user_id 유저 아이디
   * @param password 유저 비밀번호
   * @returns User
   */
  async validateUser(user_id: string, password: string): Promise<any> {
    console.log('AuthService');

    const user = await this.userRepository.findByLogin(user_id, password);

    //사용자가 요청한 비밀번호와 DB에서 조회한 비밀번호 일치여부 검사
    if (user && user.password === password) {
      const { password, ...result } = user;

      //비밀번호를 제외하고 유저 정보를 반환
      return result;
    }
    return null;
  }
}

 

아까 생성한 auth.serivce.ts 파일에는 실제 DB에 접근해서 유저를 조회하고 로그인 비즈니스 로직을 구현합니다.(현재 비밀번호 암호화는 따로 하지 않았습니다. 다음 시간에 진행하겠습니다.)

 

 

- user.repository.ts

  //로그인 유저 조회
  async findByLogin(user_id: string, password: string): Promise<User> {
    const user = await this.findOne({ where: { user_id, password } });

    if (!user) {
      throw new ForbiddenException('아이디와 비밀번호를 다시 확인해주세요.');
    }

    return user;
  }

user.repository 안에서 해당 메서드를 추가해 클라이언트에게 받은 아이디와 비밀번호를 통해 유저를 찾습니다.

 

- user.controller.ts

  @UseGuards(LocalAuthGuard)
  @Post('/auth/login')
  async login(@Request() req) {
    console.log('Login Route');

    return req.user;
  }

 

Nest.js에서는 Guards라는 기능을 제공합니다. 인증을 기능을 구현할 때 사용합니다.

 

간단하게 공식 홈페이지 내용을 확인해보면 Guardsms 해당 @Post Route Handler가 실행되기 전에 실행된다고 나와있습니다.

 

출처 :&amp;nbsp;https://docs.nestjs.kr/guards

 

실제 요청을 진행하는 계층을 확인하면 아래 순서로 진행됩니다.

 

1. LocalStrategy -> 2. AuthService -> 3. Login Route -> 4. Client

 

- 요청 및 응답

성공

 

실패

 

이번 시간에는 Passport LocalStrategy 방식에 대해 알아봤습니다. 꼭 실습을 통해 확인해보시는 걸 추천드리겠습니다.

소스 저장소

github: https://github.com/Ryan-Sin/Node_Nest/tree/v7

 

GitHub - Ryan-Sin/Node_Nest

Contribute to Ryan-Sin/Node_Nest development by creating an account on GitHub.

github.com