기술과 산업/언어 및 프레임워크

NestJS 마스터 시리즈 15화. 인증 시스템 구현 (1) – JWT 기반 로그인 시스템 만들기

B컷개발자 2025. 5. 27. 14:22
728x90

"인증은 단순한 로그인이 아니라, 서비스와 사용자 사이의 신뢰 계약이다"

NestJS에서 JWT 인증 시스템을 구현하는 방법을 설명합니다. PassportModule 연동, JWT 전략 구성, 로그인 및 토큰 발급 로직까지 실무 수준으로 정리합니다.

 

인증이란 무엇인가?

  • 인증(Authentication): 사용자가 누구인지 확인하는 과정
  • 인가(Authorization): 인증된 사용자가 특정 리소스에 접근할 수 있는지 판단하는 과정

이번 글에서는 먼저 인증, 특히 로그인과 토큰 발급 흐름에 집중한다.


1. 필요한 패키지 설치

npm install @nestjs/passport @nestjs/jwt passport passport-jwt
npm install --save-dev @types/passport-jwt
  • passport: 인증 전략을 통합 관리하는 미들웨어
  • passport-jwt: JWT 인증 전략 모듈
  • @nestjs/jwt: NestJS용 JWT 발급 및 검증 유틸

2. AuthModule 구성

nest generate module auth
nest generate service auth
nest generate controller auth

auth.module.ts에 다음과 같이 JWT 모듈을 등록한다.

@Module({
  imports: [
    JwtModule.register({
      secret: 'jwt-secret-key', // 운영 환경에선 .env로 관리
      signOptions: { expiresIn: '1h' },
    }),
    PassportModule,
    UsersModule,
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
})
export class AuthModule {}

3. JwtStrategy 정의

JWT를 검증하고, 사용자 정보를 Request 객체에 주입하는 전략이다.

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private usersService: UsersService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'jwt-secret-key',
    });
  }

  async validate(payload: any) {
    return this.usersService.findOne(payload.sub);
  }
}
  • 토큰에서 sub(사용자 ID) 값을 추출해 DB에서 사용자 정보를 조회
  • 컨트롤러에서 @Request()나 @User() 커스텀 데코레이터를 통해 접근 가능

4. AuthService 구현 – 로그인 및 토큰 발급

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
  ) {}

  async validateUser(email: string, password: string): Promise<any> {
    const user = await this.usersService.findByEmail(email);
    if (user && user.password === password) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

  async login(user: any) {
    const payload = { username: user.email, sub: user.id };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}

5. AuthController – 로그인 API 구성

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

  @Post('login')
  async login(@Body() body: { email: string; password: string }) {
    const user = await this.authService.validateUser(body.email, body.password);
    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }
    return this.authService.login(user);
  }
}

요청 예시:

POST /auth/login
{
  "email": "test@example.com",
  "password": "secure123"
}

응답 예시:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs..."
}

6. 비밀번호 보안 처리 – 실무 기준

현재는 비밀번호를 평문으로 비교하고 있지만, 실제 서비스에서는 bcrypt 등을 사용해 암호화/검증을 수행해야 한다.

npm install bcrypt
npm install --save-dev @types/bcrypt

암호화 예시:

const hashed = await bcrypt.hash(password, 10);
const isMatch = await bcrypt.compare(inputPassword, hashed);

마무리 인사이트

NestJS + Passport + JWT는 실무에서도 가장 보편적이고 강력한 인증 조합이다.
이 구조를 익히고 나면 이후의 인가 처리, 권한 분리, 토큰 갱신도 자연스럽게 확장할 수 있다.

  • 인증은 단순한 토큰 발급이 아니라, 서비스 신뢰의 첫 관문
  • 구조화된 인증 플로우가 보안과 유지보수를 모두 지킨다

"인증 시스템은 처음부터 구조적으로 시작해야, 나중에 후회하지 않는다"


다음 회차 예고

NestJS 마스터 시리즈 16화. 인증 시스템 구현 (2) – AuthGuard와 역할 기반 인가 처리
JWT 토큰을 활용한 접근 제어, Role 기반 권한 분기, 커스텀 데코레이터 설계까지 설명합니다.

 

728x90