NestJs에서 미들웨어를 통해 인증처리 (with Passport)

이우길·2022년 6월 29일
3

NestJs

목록 보기
15/20
post-thumbnail

NestJs에서 Passport를 이용하여 인증미들웨어 처리하기

예제코드는 Github에 있습니다 :)

Goal

  • 기존 Guard를 이용하여 직접 구현하였던 인증 처리를 @nestjs/passport에게 위임하기

  • 데코레이터를 이용하여 Controller에서 바로 받아서 사용하기


Passport란?

Passport 는 커뮤니티에서 잘 알려져 있고 많은 프로덕션 애플리케이션에서 성공적으로 사용되는 인증 라이브러리이다. Passport는 다양한 인증 메커니즘을 구현하는 전략 에코시스템들을 가지고 있다. (현재 글에서는 jwt를 기준으로 작성)

Passport를 nestjs에서 사용하기 위해서 필요한 라이브러리들을 아래와 같이 설치한다.

yarn add @nestjs/passport passport passport-jwt
yarn add @types/passport-jwt -D

Nestjs에서 사용하기

PassportStrategy를 상속받은 provider를 구현해주면 된다. PassportStrategy를 상속받을 때 전략을 선택할 수 있으며, 해당 provider의 생성자에서 super()를 호출하여 전략의 options를 넣어줄 수 있다.


부모 생성자에 options 넣어주기

현재 jwt전략을 사용하고 있기 때문에 사용할 수 있는 options는 passport-jwtStrategyOptions에서 확인이 가능하며 아래와 같다.

export interface StrategyOptions {
  secretOrKey?: string | Buffer | undefined;
  secretOrKeyProvider?: SecretOrKeyProvider | undefined;
  jwtFromRequest: JwtFromRequestFunction;
  issuer?: string | undefined;
  audience?: string | undefined;
  algorithms?: string[] | undefined;
  ignoreExpiration?: boolean | undefined;
  passReqToCallback?: boolean | undefined;
  jsonWebTokenOptions?: VerifyOptions | undefined;
}

validate() 구현하기

validate라는 메소드를 작성하는 이유는 Passport 라이브러리가 전략에 맞게 verfiy()를 호출하지만 nestjs에서는 validate()로 구현되기 때문이다.

validate() 메소드에서 토큰의 claim을 인자로 받게되며 해당 값을 가지고 추가적인 인증처리를 진행할 수 있다.


최종코드

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      secretOrKey: "foobar", // 1
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 2
    });
  }

  validate(tokenClaim: any) { // 3
    if (...){
      throw new UnauthorizedException();
    }
    return ...
  }
}

  1. Passport에서 요청으로 들어오는 jwt를 검증할 때 사용할 secretkey를 정의.

  2. Passport로 인증할 때 jwt 토큰을 가져오는 위치를 정의한다. 현재는 Authorization: Bearer token으로 가져오기 때문에 fromAuthHeaderAsBearerToken를 사용

  3. jwt의 검증이 완료되면 토큰의 claim이 들어오며 내부적으로 claim의 값을 검증할 수 있다. 검증 후 return해준 값은 Express.Requestuser라는 프로퍼티로 값이 들어가게 된다.


Passport Module 만들기

전략을 통해 인증 처리하는 로직을 완성하였기 때문에 Passport 모듈만 생성해주면 된다.

필요한 모둘에서 Passport를 등록해주면 되지만 그렇게 되면 위에서 구현한 JwtStrategy 또한 provider로 등록을 해야한다.

원하는 것은 다른 configModule들 처럼 app.module에서 설정을 포함한 Passport 모듈을 import하고, 사용하는 module에서 Passport 모듈만 import하여 쓰기를 원하기 때문에 아래와 같이 작성하였다.

@Module({
  imports: [
    PassportModuel, // 1
    // PassportModule.register({defaultStrategy: "jwt"}),
  ],
  providers: [JwtStrategy], // 2
  exports: [PassportModule],
})
export class PassportConfigModule {}
  1. Passport module을 import 한다. 만약 기본 전략을 설정하고 싶다면 defaultStrategy를 설정하면 된다.

  2. provider로 구현한 전략을 추가해준다.


Controller에 적용하기

@nestjs/passport에서 제공하는 AuthGuard()를 이용하여 적용할 수 있다.

@Controller()
export class UsersController {
  @Get("/test")
  @UseGuards(AuthGuard("jwt")) // 1
  test(@Req() req: Request) {
    console.log(req);
  }
}
  1. 첫번째 파라미터로 사용할 전략을 받으며 입력하지 않은 경우 Passport 모듈에서 정의한 기본전략을 따르게 된다.

만약 Passport에서 인증처리가 실패하는 경우 아래와 같은 응답을 받을 수 있다.

{
	"statusCode": 401,
	"message": "Unauthorized"
}

Request에서 user를 꺼내 사용하기

인증 처리가 통과된다면 validate()의 인자로 토큰의 cliam이 들어오게 된며 위에서 설명하였듯 Express.Requestuser 프로퍼티로 들어가게 된다.

이전 Custom Decorator에 대한 글을 참조하면 쉽게 만들 수 있으며 코드는 아래와 같다.

export const CtxUser = createParamDecorator(
  (_: unknown, ctx: ExecutionContext): ContextUser => {
    const req = ctx.switchToHttp().getRequest(); // 1
    return req.user; // 2
  }
);
  1. ExecutionContext에서 Express.Request를 얻어온다.

  2. req에 실려있는 user를 return한다.


Controller에서는 아래와 같이 사용하면 validate()에서 return한 값을 그대로 꺼내다 사용할 수 있다.

@Controller()
export class UsersController {
  @Get("/test")
  @UseGuards(AuthGuard("jwt"))
  test(@CtxUser() user: ContextUser) {
    console.log(req);
  }
}

마무리

토큰기반 인증 구현하기부터 현재 글을 통해 nestjs에서 전반적인 인증처리를 어떻게 해야하는지 살펴보았다.

추 후에는 권한처리를 하는 방식으로 돌아오겠다.


REFERENCE

profile
leewoooo

1개의 댓글

comment-user-thumbnail
2022년 6월 29일

(비밀글)

답글 달기