Nodejs

 

 

NestJS를 기반으로 한 인증 시스템을 구현하며, 회원가입과 로그인 기능을 개발한 과정을 기술 블로그 형식으로 자세히 정리합니다. 이 글에서는 강의에서 제공한 AppModule, AuthModule, AuthService, AuthController 등의 소스코드를 기반으로 전체 흐름과 각 구성 요소의 역할을 설명합니다.

 

 

 

1. 프로젝트 구조 및 환경 설정

1-1. 환경 설정

 

AppModule에서는 프로젝트 전역 설정과 모듈 구성을 담당합니다.

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        REFRESH_TOKEN_SECRET: Joi.string().required(),
        ACCESS_TOKEN_SECRET: Joi.string().required(),
        HTTP_PORT: Joi.number().required(),
        DB_URL: Joi.string().required(),
      }),
    }),
    TypeOrmModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        url: configService.getOrThrow('DB_URL'),
        autoLoadEntities: true,
        synchronize: true,
      }),
      inject: [ConfigService],
    }),
    UserModule,
    AuthModule,
  ],
})
export class AppModule {}
  • ConfigModule로 환경 변수를 정의하고, Joi를 통해 스키마 유효성 검증을 수행합니다.

  • TypeOrmModule로 PostgreSQL 연결 및 엔티티 자동 로딩을 설정합니다.

 

 

 

2. Auth 모듈 구성

2-1. AuthModule

@Module({
  imports: [
    JwtModule.register({}),
    TypeOrmModule.forFeature([User]),
    UserModule,
  ],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}
  • JwtModule을 임포트하여 JWT 발급 기능을 사용할 수 있도록 준비합니다.

  • UserModule, User 엔티티를 함께 불러와 의존성을 주입받습니다.

 

 

 

3. AuthController: API 엔드포인트 구현

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('register')
  @UsePipes(ValidationPipe)
  registerUser(@Authorization() token: string, @Body() registerDto: RegisterDto) {
    if (token === null) throw new UnauthorizedException('토큰을 입력해주세요!');
    return this.authService.register(token, registerDto);
  }

  @Post('login')
  @UsePipes(ValidationPipe)
  loginUser(@Authorization() token: string) {
    if (token === null) throw new UnauthorizedException('토큰을 입력해주세요!');
    return this.authService.login(token);
  }
}
  • register와 login 엔드포인트는 Authorization 헤더를 통해 전달받은 token을 사용합니다.

  • @Authorization() 데코레이터를 통해 토큰을 추출합니다. (별도 구현 필요)

  • 등록과 로그인은 AuthService에 위임되어 처리됩니다.

 

 

 

4. AuthService: 핵심 비즈니스 로직

4-1. 회원가입

async register(rawToken: string, registerDto: any) {
  const { email, password } = this.parseBasicToken(rawToken);
  return this.userService.create({ ...registerDto, email, password });
}
  • Basic 방식으로 인코딩된 토큰을 디코딩하여 이메일과 비밀번호를 파싱합니다.

  • registerDto의 나머지 정보와 함께 유저 생성 요청을 보냅니다.

 

 

 

 

4-2. 로그인

async login(rawToken: string) {
  const { email, password } = this.parseBasicToken(rawToken);
  const user = await this.authenticate(email, password);
  return {
    refreshToken: await this.issueToken(user, true),
    accessToken: await this.issueToken(user, false),
  };
}
  • 이메일/비밀번호 인증 후, access token과 refresh token을 각각 발급하여 반환합니다.

 

 

 

 

4-3. 토큰 파싱

parseBasicToken(rawToken: string) {
  const basicSplit = rawToken.split(' ');
  if (basicSplit.length !== 2 || basicSplit[0].toLowerCase() !== 'basic') {
    throw new BadRequestException('토큰 포맷이 잘못됐습니다!');
  }
  const decoded = Buffer.from(basicSplit[1], 'base64').toString('utf-8');
  const [email, password] = decoded.split(':');
  if (!email || !password) {
    throw new BadRequestException('토큰 포맷이 잘못됐습니다!');
  }
  return { email, password };
}
  • 클라이언트에서 base64로 인코딩한 email:password 형태의 토큰을 디코딩합니다.

 

 

 

4-4. 사용자 인증

async authenticate(email: string, password: string) {
  const user = await this.userRepository.findOne({
    where: { email },
    select: { id: true, email: true, password: true },
  });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    throw new BadRequestException('잘못된 로그인 정보입니다!');
  }
  return user;
}

 

 

 

 

4-5. JWT 발급

async issueToken(user: any, isRefreshToken: boolean) {
  const secret = isRefreshToken
    ? this.configService.getOrThrow<string>('REFRESH_TOKEN_SECRET')
    : this.configService.getOrThrow<string>('ACCESS_TOKEN_SECRET');

  return this.jwtService.signAsync(
    {
      sub: user.id,
      role: user.role,
      type: isRefreshToken ? 'refresh' : 'access',
    },
    {
      secret,
      expiresIn: '3600h',
    },
  );
}
  • JWT 페이로드에는 사용자 ID, 권한(role), 토큰 타입 정보를 담아 생성합니다.

 

 

 

 

5. 마무리 

  • 인증 방식은 Bearer가 아닌 Basic 기반 토큰을 사용하여 이메일/비밀번호를 전달받는 독특한 구조입니다.

  • NestJS의 ConfigModule, JwtModule, TypeOrmModule, ValidationPipe, Exception 처리 방식 등 실무에서 자주 쓰이는 기능들을 실습할 수 있었습니다.

  • 단순하지만 강력한 인증 로직을 통해 NestJS 구조의 장점을 잘 활용한 예제였습니다.

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

약방에 감초 , [한방에 꼭 들어가는 약재인 감초처럼] 어떤 일에나 빠짐없이 끼여드는 사람, 또는 사물을 이르는 말.

댓글 ( 0)

댓글 남기기

작성