이 문서는 NestJS에서 GraphQL 요청에 대해 JWT 인증을 적용하고, 인증된 사용자 정보를 @User('id') 데코레이터 등을 통해 Resolver로 전달하는 전체 흐름을 설명한다. REST와 GraphQL의 인증 흐름 차이점, GqlJwtAuthGuard, JwtStrategy, req.user가 설정되는 과정 등을 모두 포함하며, 실전 예제 코드도 함께 제시한다.
GraphQL 요청에 대해 다음과 같은 흐름으로 인증이 수행된다.
GraphQL HTTP 요청
└── Authorization: Bearer <access_token> 헤더 포함
↓
@UseGuards(GqlJwtAuthGuard) (Resolver에 명시적으로 지정)
↓
Passport의 AuthGuard('jwt') 내부적으로 실행됨
↓
GqlJwtAuthGuard.getRequest() → GraphQL context에서 req 추출
↓
Passport가 JWT 추출 및 서명 검증
↓
JwtStrategy.validate(payload) 호출됨
↓
리턴된 사용자 정보가 req.user 에 자동 저장됨
↓
GraphQLModule context 설정에서 context.user로 전달됨
↓
@User('id') 데코레이터로 userId 추출됨
GqlJwtAuthGuardimport { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';
@Injectable()
export class GqlJwtAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const gqlCtx = GqlExecutionContext.create(context);
return gqlCtx.getContext().req;
}
}
GraphQL 요청에서 HTTP request를 추출하기 위해
getRequest()를 오버라이드한다.
JwtStrategyimport { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: any): Promise<any> {
return { id: payload.sub, email: payload.email };
}
}
validate(payload)는 JWT 복호화가 성공한 경우 호출되며req.user에 저장된다GraphQLModule.forRoot({
context: ({ req }: { req: Request }) => ({
user: req.user,
}),
});
req.user는 JwtStrategy.validate()가 리턴한 값이다user로 전달한다@User() 커스텀 데코레이터import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
export const User = createParamDecorator((data: string, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
const user = ctx.getContext().user;
return data ? user?.[data] : user;
});
@Resolver()
@UseGuards(GqlJwtAuthGuard)
export class CategoryResolver {
@Query(() => [GraphqlCategoryDto])
async categories(@User('id') userId: string) {
return await this.categoryService.getCategories(userId);
}
}
validate()는 언제 호출되는가?Passport가 JWT 복호화에 성공하면 자동으로 호출된다. 이 시점에서 req.user가 세팅된다.
getRequest()에서 리턴한 req 객체가 validate()에 들어가는가?아니다. getRequest()는 단지 Passport가 사용할 req 객체를 제공할 뿐이다. 실제 JWT 파싱 및 validate() 호출은 Passport 내부에서 수행된다.
req.user는 어떻게 설정되는가?JwtStrategy.validate()의 리턴값이 req.user에 자동으로 들어간다.
req.user 대신 req.userInfo 같은 다른 이름을 쓸 수 있는가?기본적으로 Passport는 무조건 req.user라는 키에 값을 넣는다. NestJS에서는 이 키 이름을 변경할 수 없다.
@UseGuards(GqlJwtAuthGuard)는 Resolver에서 명시적으로 붙여야 작동한다Authorization: Bearer <token> 형태로 전달해야 한다JwtStrategy.validate()의 리턴값은 자동으로 req.user에 들어가며, GraphQL context로 전달되어 @User()로 꺼낼 수 있다