개발일지

[NestJS] 로그인 구현 #1 - 유효성 검증 본문

NestJS, Node.js/#01 Project - 투표 커뮤니티

[NestJS] 로그인 구현 #1 - 유효성 검증

lyjin 2022. 10. 27.

로그인 요구사항

  • 이메일, 비밀번호를 입력받습니다.
  • 유효성 검사
    • 각 필드 빈칸 체크
    • 이메일 가입 여부 확인
    • 비밀번호 일치 확인
  • 인증 방식으로 jwt를 사용합니다.
    • 로그인 시 access token, refresh token 발급
    • access token 기한 만료 시 재발급

 

Modeling

refresh token 검증 시 db에 저장된 refresh token 값과의 일치 여부를 확인할 계획입니다. refresh token을 관리할 RefreshTokens를 생성해줍니다. Users와 일대일 관계를 가집니다.

 

# users.prisma

model RefreshTokens {
    token  String @unique
    user   Users  @relation(fields: [userId], references: [id])
    userId Int    @unique

    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt

    @@map("refresh-tokens")
}

model Users {
    ...
    refreshToken RefreshTokens?
}

 

DTO

로그인 시 입력받을 데이터에 대한 dto를 생성합니다.

 

export class SignInUserDto {
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @IsNotEmpty()
  password: string;
}

 


SignIn 유효성 검증

signIn의 전반적인 로직은 다음과 같습니다.

 

  // users/users.service.ts

  async signIn(data: SignInUserDto): Promise<SignIn> {
    // 1-1. 유효성 검사 - 가입 여부 확인
    // 1-2. 유효성 검사 - 비밀번호 일치 여부 확인
    // 2. 토큰 생성
    ...

 

가입 여부 확인

해당 이메일로 가입한 유저가 존재하는 지 확인해야합니다. 전에 만들어놨던 findUserByWhereOption 메소드를 사용합니다. where 조건으로 email을 사용하기위해 WhereOptionByUserEmail를 정의하고 WhereOption으로 추가합니다.

 

// common/interface/users.interface.ts

export type WhereOptionByUserEmail = {
  email: string;
};

export type WhereOption =
  | WhereOptionByUserId
  | WhereOptionByUserEmail
  | WhereOptionByUserNickName;

 

 

구현한 내용은 다음과 같습니다. 유저가 존재하지 않을 경우 error를 반환합니다.

 

  async signIn(data: SignInUserDto): Promise<SignIn> {
    // 1-1. 유효성 검사 - 가입 여부 확인
    const whereOption: WhereOptionByUserEmail = {
      email: data.email,
    };
    const user: Users = await this.usersRepository.findUserByWhereOption(
      whereOption,
    );

    if (!user) {
      throw new CustomException(UsersException.USER_NOT_EXIST);
    }

    // 1-2. 유효성 검사 - 비밀번호 일치 여부 확인
    // 2. 토큰 생성
  }

 

비밀번호 일치 여부 확인

  async signIn(data: SignInUserDto): Promise<SignIn> {
    // 1-1. 유효성 검사 - 가입 여부 확인
    ...

    // 1-2. 유효성 검사 - 비밀번호 일치 여부 확인
    await this._validatePassword(data.password, user.password, 'signIn');

    // 2. 토큰 생성
  }

 

 

회원가입에서 사용한 _validatePassword 메소드를 사용했습니다.

 

// 원래의 _validatePassword
  private _validatePassword(password: string, checkPassword: string) {
    if (password !== checkPassword) {
      throw new CustomException(UsersException.NOT_MATCHED_PASSWORD);
    }
  }

 

 

signIn, signUp을 구분하기 위한 파라미터 type을 추가하고 조건문으로 처리합니다. 비밀번호는 db에 암호화되어 저장되기 때문에 bcrypt.compare() 로 비교해야합니다.

 

// 수정된 _validatePassword
  private async _validatePassword(
    password: string,
    checkPassword: string,
    type: ValidatePasswordType = 'signUp',
  ) {
    if (type == 'signUp' && password !== checkPassword) {
      throw new CustomException(UsersException.NOT_MATCHED_PASSWORD);
    }

    if (type == 'signIn' && !(await bcrypt.compare(password, checkPassword))) {
      throw new CustomException(UsersException.NOT_MATCHED_PASSWORD);
    }
  }