12. 코드로 만드는 인증_service

수원 개발자·2023년 12월 11일
0

NestJS

목록 보기
14/15
post-thumbnail

만드려는 기능

1) registerWithEmail

  • email, nickname, password를 입력받고 사용자를 생성한다.
  • 생성이 완료되면 accessToken과 refreshToken을 반환한다.
    회원가입 후 바로 반환해서 바로 로그인하도록 지정

2) loginWithEmail

  • email, password를 입력하면 사용자 검증을 진행한다.
  • 검증이 완료되면 accessToken과 refreshToken을 반환한다

3) loginUser

  • (1)과 (2)에서 필요한 accessToken과 refreshToken을 반환하는 로직

4) signToken

  • (3)에서 필요한 accessToken과 refreshToken을 sign하는 로직

5) authenticatedWithEmailAndPassword

  • (2)에서 로그인을 진행할 때 필요한 기본적인 검증 진행
  1. 사용자가 존재하는지 확인. email
  2. 비밀번호가 맞는지 확인
  3. 모두 통과되면 찾은 사용자 정보 반환
  4. loginWithEmail에서 반환된 데이터를 기반으로 토큰 생성

signToken

가장 작은 단위인 4번 signToken 먼저 진행해보도록 하겠다.

@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}

  /**
   * Payload에 들어갈 정보
   *
   * 1) email
   * 2) sub -> id
   * 3) type -> access | refresh
   */

  signToken(user: Pick<UsersModel, 'email' | 'id'>, isRefreshToken: boolean) {
    const payload = {
      email: user.email,
      sub: user.id,
      type: isRefreshToken ? 'refresh' : 'access'
    };

    return this.jwtService.sign(payload, {
      secret: JWT_SECRET,
      expiresIn: isRefreshToken ? 3600 : 300,
    });
  }
}

UserModel에서 모든 정보를 가져올 필요가 없으므로 pick을 통해 필요한 프로퍼티만 받고 isRefreshToken을 만들어서 참이면 refresh, 거짓이면 access로 받기로 했다. 또한 secret 키는 환경변수를 설정해줘서 간접적으로 적었다. access token은 만료기간을 길게할 필요가 없으므로 5분으로 하고 refresh token일 경우는 1시간으로 잡았다.

loginUser

loginUser(user: Pick<UsersModel, 'email' | 'id'>) {
    return {
      accessToken: this.signToken(user, false),
      refreshToken: this.signToken(user, true)
    }
  }

위에서 만든 signToken을 사용해서 accesstoken, refreshtoken을 반환하도록 함수를 작성했다.

authenticatedWithEmailAndPassword

// usersService

async getUserByEmail(email: string) {
    return this.usersRepository.findOne({
      where: {
        email,
      }
    })
  }
  

async authenticatedWithEmailAndPassword(
    user: Pick<UsersModel, 'email' | 'password'>,
  ) {
    // 1. 사용자가 존재하는지 확인. email
    // 2. 비밀번호가 맞는지 확인
    // 3. 모두 통과되면 찾은 사용자 정보 반환
    // 4. loginWithEmail에서 반환된 데이터를 기반으로 토큰 생성
    const existingUser = await this.usersService.getUserByEmail(user.email);

    if (!existingUser) {
      throw new UnauthorizedException('존재하지 않는 사용자입니다.');
    }

    // 파라미터
    // 1) 입력된 비밀번호
    // 2) 기존 해시 -> 사용자 정보에 저장되어있는 해시
    const passOk = await bcrypt.compare(user.password, existingUser.password);

    if(!passOk) {
      throw new UnauthorizedException('비밀번호가 틀렸습니다.');
    }

    return existingUser;
  }

usersService에 getUserByEmail이라는 함수를 만들고 email 파라미터를 받아서 데이터에 email이 있는지 확인했다. 이를 통해 사용자가 존재하면 existingUser에 값을 넣어주고 아닐 경우에는 에러를 던지게 했다. bcrypt.compare을 통해 패스워드 또한 비교해서 해싱된 값과 맞는지 확인해서 패스워드를 통과하면 반환되도록 했다.

loginWithEmail

async loginWithEmail(user: Pick<UsersModel, 'email' | 'password'>) {
    const existingUser = await this.authenticatedWithEmailAndPassword(user);

    return this.loginUser(existingUser);
  }
}

지금까지 만든 코드를 이용해서 모든 과정을 통과하면 기존의 이메일이 있고 패스워드도 맞은 상황인 것이다. 그러면 로그인을 시켜주면 된다. 이에 관한 함수를 만들었다.

registerWithEmail

async createUser(user: Pick<UsersModel, 'email'|'nickname'|'password'>){
    // 1) nickname 중복이 없는지 확인
    // exist() -> 조건에 해단되는 값이 있으면 true 반환
    const nicknameExists = await this.usersRepository.exist({
      where: {
        nickname: user.nickname,
      },
    });

    if(nicknameExists) {
      throw new BadRequestException('이미 존재하는 닉네임입니다!');
    }

    const emailExists = await this.usersRepository.exist({
      where: {
        email: user.email,
      }
    });

    if(emailExists) {
      throw new BadRequestException('이미 존재하는 이메일입니다!');
    }


    const userObject = this.usersRepository.create({
      nickname: user.nickname, email: user.email, password: user.password,
    });

    const newUser = await this.usersRepository.save(user);

    return newUser;
  }

기존에 userController에서 userService에 새로운 createUser 함수를 만들고 이를 컨트롤러에서 적용시켜줬다.

또한 auth.service.ts에서 registerWithEmail 함수를 만들었다.

async registerWithEmail(user: Pick<UsersModel, 'email' | 'nickname' | 'password'>) {
    const hash = await bcrypt.hash(user.password, HASH_ROUNDS);

    const newUser = await this.usersService.createUser(user);

    return this.loginUser(newUser);
  }

이를 통해 새로운 유저를 이메일, 닉네임, 비밀번호 파라미터를 받아서 생성하도록 했다.

0개의 댓글

관련 채용 정보