NestJS를 사용한 웹 애플리케이션에서는 종종 인증 및 권한 부여를 처리하기 위해 JWT(JSON Web Token)를 사용합니다. 이를 효율적으로 구현하기 위해서는 미들웨어(Middleware)를 활용하여 각 요청에서 사용자의 토큰을 검증하고, 유효한 사용자를 식별하는 과정이 필요합니다. 이번 포스트에서는 JWT를 활용한 사용자 인증을 미들웨어로 구현하는 방법을 알아보겠습니다.
미들웨어는 요청(request)과 응답(response) 사이에 위치하여 라우트 핸들러 전에 호출되는 함수입니다. 이 함수는 request
와 response
객체에 접근할 수 있고, 애플리케이션의 request-response 주기에 있는 다음(next) 미들웨어 함수로 제어권을 넘길 수 있습니다.
NestJS에서 미들웨어는 익스프레스 미들웨어와 동일한 방식으로 작동하며, 다음 두 가지 형태로 사용자 정의 미들웨어를 구현할 수 있습니다:
@Injectable()
데코레이터가 적용된 클래스자세한 내용은 NestJS 공식 문서 - Middleware에서 확인할 수 있습니다.
JWT 토큰을 생성하고 검증하기 위해 jsonwebtoken
패키지를 사용합니다. 먼저 패키지를 설치합니다.
npm i jsonwebtoken
npm i @types/jsonwebtoken -D
토큰을 검증하기 위해 JwtService
클래스를 구현합니다. 이 클래스는 토큰을 생성하고, 검증하며 페이로드를 반환하는 역할을 합니다.
import * as jwt from 'jsonwebtoken';
import { Injectable } from '@nestjs/common';
@Injectable()
export class JwtService {
private readonly secretKey = process.env.SECRET_KEY || 'default_secret';
sign(payload: any): string {
return jwt.sign(payload, this.secretKey);
}
verify(token: string): any {
return jwt.verify(token, this.secretKey);
}
decode(token: string): any {
return jwt.decode(token);
}
}
JWT 미들웨어는 다음과 같은 역할을 수행합니다:
1. 요청 헤더에서 JWT 토큰을 추출합니다.
2. 토큰을 JwtService.verify()
메서드를 사용하여 검증하고 페이로드를 반환합니다.
3. 반환된 페이로드를 이용해 사용자를 찾습니다.
4. 찾은 사용자의 정보를 요청 객체에 저장하여 다음 미들웨어 또는 컨트롤러에서 사용할 수 있도록 합니다.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { JwtService } from './jwt.service';
import { UsersService } from './users.service';
@Injectable()
export class JwtMiddleware implements NestMiddleware {
constructor(
private readonly jwtService: JwtService,
private readonly usersService: UsersService
) {}
async use(req: Request, res: Response, next: NextFunction) {
if ('x-jwt' in req.headers) {
const token = req.headers['x-jwt'].toString();
try {
const decoded = this.jwtService.verify(token);
if (typeof decoded === 'object' && decoded.hasOwnProperty('id')) {
const { user, ok } = await this.usersService.findById(decoded['id']);
if (ok) {
req['user'] = user;
}
}
} catch (error) {
// 토큰 검증 실패시 로그 처리 (필요에 따라 에러 처리)
console.error('JWT verification failed:', error.message);
}
}
next();
}
}
UsersService
는 실제로 데이터베이스에서 사용자를 조회하고 성공 여부를 반환합니다. 다음은 UsersService
의 예시입니다.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
@Injectable() // 이 클래스가 서비스로서 NestJS에 의해 인스턴스화되고 관리될 수 있음을 나타냄
export class UsersService {
constructor(
@InjectRepository(User) private readonly users: Repository<User>,
private readonly jwtService: JwtService,
) { }
async login({ email, password }: LoginInput): Promise<LoginOutput> {
try {
const token = this.jwtService.sign(user.id);
return {
ok: true,
token,
};
} catch (error) {
return {
ok: false,
error: "사용자가 로그인할 수 없습니다."
}
}
}
}
NestJS에서 미들웨어를 통해 JWT 인증을 구현하면 각 요청에서 반복적으로 토큰을 검증하고 사용자 정보를 가져오는 과정을 간편하게 처리할 수 있습니다. 이로써 애플리케이션 전반에 걸쳐 안전하고 일관된 인증 메커니즘을 제공할 수 있습니다.