kimyu0218
  • [etc] passport를 이용하여 인증/인가 구현하기 (with nest)
    2024년 01월 18일 02시 20분 04초에 업로드 된 글입니다.
    작성자: @kimyu0218

    passport로 인증/인가 구현하기

    passport란?

    직접 구현한 인증/인가 로직을 passport 라이브러리를 활용하여 대체할 것이다. passport는 node에서 사용하는 인증 라이브러리다.

    이미 인증/인가를 구현했으면서 왜 passport를 도입했을까? 바로 passport가 표준화된 방식을 이용하고 있기 때문이다.

    passport의 이점
    • 표준화된, 다양한 인증 전략 제공 : 직접 다 구현할 필요없이 다양한 전략을 간단하게 사용할 수 있다.
    • 풍부한 생태계 : 많은 곳에서 활발하게 사용되고 있기 때문에 문제가 발생했을 때 도움을 얻기 쉽다.
    ...
    @Injectable()
    export class JwtAuthGuard implements CanActivate {
      constructor(private readonly jwtService: JwtService) {}
    
      canActivate(
        context: ExecutionContext,
      ): boolean | Promise<boolean> | Observable<boolean> {
        const req: any = context.switchToHttp().getRequest();
        const token: string | null = req.cookies.magicconch;
        if (!token) {
          throw new UnauthorizedException(ERR_MSG.JWT_NOT_FOUND);
        }
        const decoded: JwtPayloadDto = this.verifyToken(token);
        req.user = decoded;
        return true;
      }
    
      private verifyToken(token: string): JwtPayloadDto {
        try {
          return this.jwtService.verify(token);
        } catch (err: unknown) {
          this.handleVerificationError(err);
          throw err;
        }
      }
      ...
    }

    위의 코드는 passport를 도입하기 전의 `JwtAuthGuard`다. 쿠키에 붙은 jwt 토큰을 검증하고, 검증된 사용자 정보를 요청 객체에 추가하여 해당 요청의 인증 상태를 확인한다.

     

    nest에서 passport 사용하기

    먼저 몇 가지 패키지를 설치해야 한다.

    $ npm install --save @nestjs/passport passport passport-jwt
    $ npm install --save-dev @types/passport-jwt

    passport의 다양한 전략들을 이용하기 위해서는 `@nestjs/passport`와 `passport`를 설치해야 한다. 앞에서 보았듯이 jwt를 이용하고 있으므로 `passport-jwt` 전략도 설치해줬다.

     

    passport 전략 구현하기

    앞서 말했듯이 passport 라이브러리는 표준화된 다양한 전략을 제공한다. passport에 다음 두 가지를 설정하여 내 프로젝트에 맞는 전략을 사용할 수 있다.

    1. 옵션 설정 (ex. jwt 전략의 경우, 토큰 서명에 사용되는 비밀키를 전달)
    2. 검증 콜백 설정
    import { Injectable } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';
    import { PassportStrategy } from '@nestjs/passport';
    import { ExtractJwt, Strategy } from 'passport-jwt';
    ...
    @Injectable()
    export class JwtStrategy extends PassportStrategy(Strategy) {
      constructor(private readonly configService: ConfigService) {
        // 1. passport-jwt에 필요한 옵션 전달
        super({
          jwtFromRequest: ExtractJwt.fromExtractors([ // req에서 jwt를 추출하는 방법 지정 
            (req: Request) => req.cookies.magicconch,
          ]),
          ignoreExpiration: false, // 만료 확인을 passport 모듈에 위임
          secretOrKey: configService.get('JWT_SECRET_KEY'), // 토큰 서명에 사용하는 비밀키
        });
      }
      
      /*
        2. 검증 콜백 설정
        jwt-strategy를 사용하면... 
        - 먼저 passport에서 jwt 서명을 확인하고, JSON을 디코딩한다.
        - 그 후 validate() 콜백이 실행되고, 반환값이 req 객체의 속성으로 추가된다.
      */
      async validate(payload: any): Promise<JwtPayloadDto> {
        return {
          email: payload.email,
          providerId: payload.providerId,
          accessToken: payload.accessToken,
        };
      }
    }

    코드의 주석에서도 확인할 수 있듯이 passport가 요청 객체에서 데이터를 가져와 이를 검증하고 디코딩하는 과정을 대신 수행해준다. `AuthGuard`의 `canActivate`, `verifyToken`이 더 이상 필요하지 않다.

    앞서 작성한 전략을 인증 가드에서 사용할 예정이기 때문에 다른 모듈이나 컴포넌트에서 사용할 수 있도록 공급자에 추가한다.

     

    passport로 인증 가드 간단하게 수정하기

    먼저, 앞서 작성한 전략을 인증 가드에서 사용할 예정이기 때문에 다른 모듈이나 컴포넌트에서 사용할 수 있도록 공급자에 추가해준다.

    import { Module } from '@nestjs/common';
    import { PassportModule } from '@nestjs/passport';
    import { TypeOrmModule } from '@nestjs/typeorm';
    ...
    import { AuthService } from './service/auth.service';
    import { JwtStrategy } from './strategies/jwt.strategy';
    
    @Module({
      imports: [... PassportModule], // PassportModule 가져오기
      controllers: [AuthController],
      providers: [... JwtStrategy], // JwtStrategy 공급자로 추가하기
      ...
    })
    export class AuthModule {}

    이제 `@nestjs/passport`를 이용하여 앞서 만든 전략을 적용해보자. jwt 전략을 사용하고 있으므로 `AuthGuard`에 jwt를 넘겨주면 된다.

    import { Injectable } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    
    @Injectable()
    export class JwtAuthGuard extends AuthGuard('jwt') {}

    아까와 달리 매우 깔끔해진 것을 확인할 수 있다!!


    참고자료

    댓글