기존에는 억세스 토큰을 검증하고, 인증되지 않은 요청에 대해 곧바로 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) {...}