NestJS에서 JWT(JSON Web Token), Passport, Strategy 패턴을 사용하여 인증을 구현하는 방법에 대해 알아보고, 각 패턴의 장점과 구현 방식, 그리고 더 나아가 활용할 수 있는 방향에 대해 논의합니다.
JWT(JSON Web Token)
JWT(JSON Web Token)는 웹 표준(RFC 7519)으로서, 당사자 간에 JSON 객체로 정보를 안전하게 전송하는 방법을 정의합니다. JWT는 암호화된 토큰을 사용하여 정보를 안전하게 전달하며, 주로 인증 및 권한 부여에 사용됩니다. NestJS에서는 @nestjs/jwt
패키지를 통해 JWT를 간편하게 구현하고 사용할 수 있습니다.
장점
- 간결성: JWT는 필요한 정보를 JSON 형태로 담아 간결하게 표현할 수 있습니다.
- 확장성: JWT는 다양한 정보를 payload에 담을 수 있어 확장성이 뛰어납니다.
- 보안성: JWT는 서명을 통해 위변조를 방지할 수 있어 안전합니다.
구현 방식
NestJS에서 JWT를 사용하려면 @nestjs/jwt
패키지를 설치해야 합니다.
npm install @nestjs/jwt
AuthService
에서 JWT를 생성하고 검증하는 로직을 구현합니다.
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private readonly jwtService: JwtService) {}
async generateToken(payload: any): Promise<string> {
return this.jwtService.sign(payload);
}
async verifyToken(token: string): Promise<any> {
return this.jwtService.verify(token);
}
}
더 활용할 수 있는 방향
- Refresh Token: Access Token의 유효 기간을 짧게 설정하고, Refresh Token을 사용하여 Access Token을 갱신하는 방식으로 보안성을 강화할 수 있습니다.
- Payload 확장: JWT payload에 사용자 정보 외에 권한 정보, 서비스 정보 등을 추가하여 다양한 용도로 활용할 수 있습니다.
Passport
Passport는 Node.js를 위한 인증 미들웨어입니다. Passport는 다양한 인증 전략(Strategy)을 제공하며, 이를 통해 로컬 인증, OAuth 2.0, OpenID Connect 등 다양한 인증 방식을 지원합니다.
장점
- 다양한 인증 방식 지원: Passport는 다양한 인증 전략을 제공하여 개발자가 원하는 인증 방식을 쉽게 구현할 수 있도록 돕습니다.
- 모듈화된 구조: Passport는 인증 로직을 모듈화하여 관리할 수 있도록 돕습니다.
- 유연성: Passport는 다양한 미들웨어와 함께 사용할 수 있어 유연성이 뛰어납니다.
구현 방식
NestJS에서 Passport를 사용하려면 @nestjs/passport
패키지를 설치해야 합니다.
npm install @nestjs/passport passport-jwt
CookieJwtStrategy
를 사용하여 JWT Strategy를 구현하고, 쿠키에서 JWT를 추출하는 방법을 설정합니다.
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-jwt';
import { Request } from 'express';
import { ConfigService } from '@nestjs/config';
const cookieExtractor = (req: Request): string | null => {
let token = null;
if (req && req.cookies) {
token = req.cookies['aToken']; // Use 'accessToken' cookie name
}
return token;
};
@Injectable()
export class CookieJwtStrategy extends PassportStrategy(Strategy, 'cookie-jwt') {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: cookieExtractor, // Use cookie extractor
ignoreExpiration: false,
passReqToCallback: true,
secretOrKeyProvider: (req, rawJwtToken, done) => {
const key = this.configService.get<string>('JWT_ACCESS_KEY');
if (!key) {
return done(new Error('JWT_ACCESS_KEY not configured'), null);
}
done(null, key);
},
});
}
async validate(req: Request, payload: any): Promise<any> {
return payload;
}
}
더 활용할 수 있는 방향
- OAuth 2.0 연동: Passport를 사용하여 Google, Facebook, Kakao 등 OAuth 2.0을 지원하는 다양한 서비스와 연동할 수 있습니다.
- Custom Strategy 구현: Passport에서 제공하는 기본 Strategy 외에 Custom Strategy를 구현하여 특정 서비스에 특화된 인증 방식을 지원할 수 있습니다.
Strategy 패턴
Strategy 패턴은 알고리즘군을 정의하고 각각을 캡슐화하여 필요에 따라 교환할 수 있게 하는 패턴입니다. Passport는 Strategy 패턴을 사용하여 다양한 인증 방식을 지원합니다.
장점
- 유연성: Strategy 패턴을 사용하면 인증 방식을 쉽게 변경할 수 있습니다.
- 확장성: 새로운 인증 방식을 추가하는 것이 용이합니다.
- 유지보수성: 인증 로직이 모듈화되어 있어 유지보수가 용이합니다.
구현 방식
CookieJwtStrategy
는 PassportStrategy
를 상속받아 JWT Strategy를 구현합니다. validate
메서드에서는 토큰을 검증하고 사용자 정보를 반환합니다.
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-jwt';
import { Request } from 'express';
import { ConfigService } from '@nestjs/config';
const cookieExtractor = (req: Request): string | null => {
let token = null;
if (req && req.cookies) {
token = req.cookies['aToken'];
}
return token;
};
@Injectable()
export class CookieJwtStrategy extends PassportStrategy(Strategy, 'cookie-jwt') {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: cookieExtractor,
ignoreExpiration: false,
passReqToCallback: true,
secretOrKeyProvider: (req, rawJwtToken, done) => {
const key = this.configService.get<string>('JWT_ACCESS_KEY');
if (!key) {
return done(new Error('JWT_ACCESS_KEY not configured'), null);
}
done(null, key);
},
});
}
async validate(req: Request, payload: any): Promise<any> {
return payload;
}
}
CookieJwtAuthGuard
는 AuthGuard
를 상속받아 인증 가드를 구현합니다. canActivate
메서드에서는 인증을 수행하고, handleRequest
메서드에서는 인증 실패 시 예외를 처리합니다. 특히, Access Token이 만료된 경우 Refresh Token을 사용하여 Access Token을 갱신하는 로직이 포함되어 있습니다.
import { Injectable, UnauthorizedException, Inject } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { clearTokenCookies } from '../utils/cookie.helper';
import { Request, Response } from 'express';
import { UserDto } from '../dto/user.dto';
@Injectable()
export class CookieJwtAuthGuard extends AuthGuard('cookie-jwt') {
constructor(
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {
super();
}
handleRequest(err: any, user: any, info: any, context: ExecutionContext, status?: any): any {
const request = context.switchToHttp().getRequest<Request>();
const response = context.switchToHttp().getResponse<Response>();
// Access Token 만료 처리
if (info instanceof TokenExpiredError) {
console.log('CookieJwtAuthGuard: Access token expired, attempting refresh...');
return this.handleTokenRefresh(request, response);
}
if (err || !user) {
clearTokenCookies(response, this.configService);
throw err || new UnauthorizedException();
}
return user;
}
}
AuthController
에서는 @UseGuards
데코레이터를 사용하여 인증 가드를 적용합니다. 다음은 AuthController
에서 인증 가드를 사용하는 예시입니다.
import { Controller, Get, UseGuards, Req, Res } from '@nestjs/common';
import { CookieJwtAuthGuard } from '@flow/auth';
import { Request, Response } from 'express';
@Controller('auth')
export class AuthController {
@Get('profile')
@UseGuards(CookieJwtAuthGuard)
getProfile(@Req() req: Request) {
return req.user;
}
}
더 활용할 수 있는 방향
- 다양한 인증 로직 구현: Strategy 패턴을 사용하여 다양한 인증 로직을 구현할 수 있습니다. 예를 들어, 사용자 IP 주소를 검증하거나, 사용자 Agent를 검증하는 등의 로직을 추가할 수 있습니다.
- Custom Strategy 구현: Passport에서 제공하는 기본 Strategy 외에 Custom Strategy를 구현하여 특정 서비스에 특화된 인증 방식을 지원할 수 있습니다.
결론
NestJS에서 JWT, Passport, Strategy 패턴을 사용하여 인증을 구현하면, 간결하고 확장 가능하며 안전한 인증 시스템을 구축할 수 있습니다. 각 패턴의 장점을 이해하고 적절하게 활용하면, 개발 생산성을 높이고 애플리케이션의 보안성을 강화할 수 있습니다.
읽어주셔서 감사합니다!