Backend/NestJS

NestJS에서 JWT 인증 구현하기

민 채 2025. 5. 15. 09:55

JWT(Json Web Token)는 웹이나 앱에서 API 통신을 할 때 가장 널리 쓰이는 방식 중 하나입니다.
NestJS에서는 @nestjs/jwtpassport-jwt를 통해 JWT 기반 인증을 쉽게 구현할 수 있습니다.

설치해야 할 패키지

npm install @nestjs/jwt passport-jwt @nestjs/passport

 


auth.module.ts

@Module({
  imports: [
    forwardRef(() => UserModule),
    TypeOrmModule.forFeature([User]),
    PassportModule,
    JwtModule.register({
      secret: process.env.JWT_SECRET ?? 'secretKey',
      signOptions: { expiresIn: '1h' },
    }),
  ],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}
  • JwtModule.register()는 JWT 서명을 위한 키와 만료 시간을 설정합니다.
  • PassportModule은 인증 전략(Strategy)을 NestJS에 통합합니다.
  • forwardRef()는 순환 참조를 해결하기 위해 사용됩니다 (예: User → Auth → User 구조).

 


jwt.strategy.ts

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET ?? 'secretKey',
    });
  }

  async validate(payload: any) {
    return {
      user_id: payload.user_id,
      email: payload.email,
    };
  }
}
  • PassportStrategy(Strategy)는 JWT 토큰 인증 전략을 정의합니다.
  • ExtractJwt.fromAuthHeaderAsBearerToken()Authorization: Bearer {token} 형태의 헤더에서 토큰을 추출합니다.
  • validate()는 JWT payload를 파싱한 뒤 요청에 포함할 유저 정보를 리턴합니다.

 


auth.service.ts

Access + Refresh Token 발급

generateTokens(payload: { user_id: number; email: string }) {
  const accessToken = this.jwtService.sign(payload, {
    secret: process.env.JWT_SECRET,
    expiresIn: '1h',
  });

  const refreshToken = this.jwtService.sign(payload, {
    secret: process.env.JWT_REFRESH_SECRET,
    expiresIn: '7d',
  });

  return { accessToken, refreshToken };
}
  • Access Token은 인증 용도 (짧은 유효기간)
  • Refresh Token은 재발급 용도 (긴 유효기간)

async saveRefreshToken(userId: number, refreshToken: string): Promise<void> {
  await this.userRepo.update(userId, { refresh_token: refreshToken });
}
  • 로그인 또는 토큰 재발급 시 DB에 Refresh Token을 저장합니다.
  • 나중에 재발급 요청이 들어올 때 검증 용도로 사용됩니다.

async removeRefreshToken(userId: number): Promise<void> {
  await this.userRepo.update(userId, { refresh_token: null });
}
  • 로그아웃 시 토큰을 DB에서 제거함으로써 인증을 만료시킵니다.

 


JWT 인증 적용 방법

@ApiTags('Like')
@Controller('/like')
export class LikeController {
  constructor(private readonly likeService: LikeService) {}

  @UseGuards(AuthGuard('jwt'))
  @ApiBearerAuth()
  @HttpPost(':postId')
  @ApiOperation({ summary: '게시글 좋아요 토글' })
  @ApiResponse({ status: 200, description: '좋아요 상태 반환' })
  async toggleLike(@Param('postId') postId: number, @Req() req: any) {
    return this.likeService.toggleLike(postId, req.user.user_id);
  }
}
  • 컨트롤러에 @UseGuards(AuthGuard('jwt'))를 적용하면 인증이 필요한 API로 만들 수 있습니다

 

.env

JWT_SECRET=access-secret-key
JWT_REFRESH_SECRET=refresh-secret-key
  • 민감한 키를 .env 파일로 관리할 수 있습니다.

 


JWT 인증 흐름

  • 로그인 시: Access + Refresh Token 발급 → Refresh Token은 DB 저장
  • API 요청 시: Authorization: Bearer {AccessToken} → JwtStrategy가 인증
  • Access Token 만료 시: Refresh Token으로 Access Token 재발급
  • 로그아웃 시: Refresh Token DB에서 제거

 


다른 방식과 비교

1. JWT 토큰 기반 인증

🔸 원리

  • 로그인 시 서버가 JWT(Access + Refresh Token)을 발급
  • 클라이언트가 토큰을 로컬 스토리지 or 메모리에 저장
  • 이후 요청마다 Authorization: Bearer <AccessToken> 헤더에 담아 전송

🔸 특징

  • Stateless → 서버는 인증 상태를 기억하지 않음
  • 수평 확장(서버 여러 대) 용이
  • Refresh Token으로 토큰 재발급 가능

🔸 단점

  • 탈취된 토큰은 무효화 어렵다 (서버에서 상태를 기억하지 않기 때문)
  • 토큰 탈취 대비를 위한 보안 설계 필요 (예: HttpOnly, 만료시간 짧게 설정)

2. 세션 기반 인증

🔸 원리

  • 로그인 시 서버가 세션을 생성하고, 고유한 세션 ID를 생성
  • 세션 ID는 브라우저 쿠키에 저장되어 매 요청 시 전송
  • 서버는 해당 세션 ID로 유저 정보를 메모리나 Redis 등에서 조회

🔸 특징

  • 서버가 상태를 유지 (Stateful)
  • 사용자 정보를 서버에 두기 때문에 보안에 좋다
  • 세션 만료 시 자동 로그아웃
  • XSS/CSRF 대응은 쿠키 보안 설정(HttpOnly, SameSite) 필요

🔸 단점

  • 서버 메모리/저장소에 사용자 수만큼 세션을 저장해야 함 → 확장성 낮음
  • 서버 간 세션 공유를 위한 Redis 등의 별도 인프라 필요

3. 쿠키 기반 인증

🔸 원리

  • 세션 ID 또는 JWT 토큰을 쿠키에 저장
  • 브라우저는 매 요청마다 자동으로 쿠키를 전송

🔸 특징

  • 인증 정보를 클라이언트가 아닌 쿠키에 저장하고, 서버가 자동으로 수신
  • JWT든 세션 ID든 모두 쿠키 안에 저장 가능

🔸 장점

  • 브라우저 자동 전송 → API 요청 시 따로 헤더 설정 안 해도 됨
  • HttpOnly + Secure 설정으로 보안 강화 가능

🔸 단점

  • 쿠키 사용 시 CSRF 공격 위험 → 반드시 SameSite=Strict나 CSRF 토큰 필요
  • 서버와 클라이언트가 같은 도메인에서 동작해야 안정적