PassportStrategy와 passReqToCallback

rabbit jack·2025년 6월 23일

Typescript

목록 보기
3/5

기존에는 억세스 토큰을 검증하고, 인증되지 않은 요청에 대해 곧바로 401 Unauthorized를 반환하는 전략만을 사용했다.

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

  async validate(payload: any) {
    const user = await this.usersService.findById(payload.sub);
    if (!user) {
      throw new UnauthorizedException('User not found');
    }
    return user; // req.userID에 추가된다.
  }
}

하지만 이후, 인증은 선택사항이며 유저 식별만 하고 싶은 상황이 생기면서, 401을 발생시키지 않고, 유저 정보만 제공하는 전략이 필요해졌다. 이를 위해 아래와 같은 Strategy와 Guard를 추가로 정의했다.

// Guard
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class OptionalJwtAuthGuard extends AuthGuard('optional-jwt') {
  handleRequest(err: any, user: any, info: any, context: ExecutionContext) {
    // 에러가 있거나 유저가 없으면 null 반환 (401 발생 안 함)
    return user || null;
  }

}
// Strategy
@Injectable()
export class OptionalJwtStrategy extends PassportStrategy(Strategy, 'optional-jwt') {
  constructor(private usersService: UsersService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
      passReqToCallback: true,
      ignoreExpiration: false,
    });
  }

  async validate(req: Request, payload: any) {
    try {
      const user = await this.usersService.findById(payload.sub);
      return user || null;
    } catch {
      return null;
    }
  }
}

이전의 Strategy와 달리 validate에서 req 객체를 첫번째 인자로 받는데, 이는 passReqToCallback 옵션 때문이다.

이를 고려하지 않으면 payload가 undefined로 전달되는 버그가 발생할 수 있다.

// passReqToCallback : false (기본값)
async validate(payload: any) {...}

// passReqToCallback : true
async validate(req: Request, payload: any) {...}

0개의 댓글