Backend/NestJS
NestJS에서 JWT 인증 구현하기
민 채
2025. 5. 15. 09:55
JWT(Json Web Token)는 웹이나 앱에서 API 통신을 할 때 가장 널리 쓰이는 방식 중 하나입니다.
NestJS에서는 @nestjs/jwt
와 passport-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 토큰 필요
- 서버와 클라이언트가 같은 도메인에서 동작해야 안정적