[NestJS] Authentication

hahaha·2021년 8월 1일
0

NestJS

목록 보기
5/11

NestJS docs - authentication

Passport

  • node.js 인증 라이브러리
  • @nestjs/passport 모듈 사용
  • 높은 수준의 Passport 절차
    1. 자격 증명(사용자 이름/암호, JWT) 확인을 통한 사용자 인증
    2. 인증 상태 관리(JWT 토큰 발행 or Express 세션 생성)
    3. 인증된 사용자에 대한 정보를 Request 객체에 첨부

인증 구현하기

1. 사용자 인증

vanilla Passport 작동 방식

  1. 해당 전략에 특정한 옵션 세트 제공
    • 서브 클래스의 super()
  2. 확인 콜백 제공
    • Passport에 사용자 저장소와 상호 작용하는 방법 알려줌
    • 사용자의 존재 여부 및 해당 자격 증명의 유효성 검사
    • 유효성 검사 성공 시, 사용자 반환 / 실패 시, null 반환
    • 하위 클래스의 validate()
// auth.service.ts
@Injectable()
export class AuthService {
  constructor(private usersService: UsersService) {}
  
  async validateUser(username: string, pass: string): Promise<any> {
    const user = await this.usersService.findOne(username);
    if (user && user.password === pass) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }
}

실제 애플리케이션에서는, 사용자 암호를 일반 텍스트로 저장하거나 노출하지 않음
-> bcrypt 와 같은 라이브러리 사용 (솔티드 단방향 해시 알고리즘)

// auth/local.strategy.ts
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super();
    
    // 옵션 객체 전달 예시
    /*super({
      secretOrKey: process.env.JWT_SECRET,
      ignoreExpiration: true,
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    });*/
    
  }
  async validate(username: string, password: string): Promise<any> {
    const user = await this.authService.validateUser(username, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

Built-in Passport Guards 적용하기

  1. 사용자가 로그인 되지 않은 경우(인증 X)
    • 인증되지 않은 사용자가 엑세스할 수 있는 경로 제한
      - 보호된 경로에 가드 사용(유효한 JWT가 있는지 확인)
    • 인증 단계 시작(이전에 인증되지 않은 사용자가 로그인을 시도할 때)
      - 유효한 사용자에게 JWT 발급
  2. 사용자가 로그인 된 경우(인증 O)
    • 보호된 경로에 엑세스할 수 있도록 함(표준 유형의 가드)
@Controller()
export class AppController {
  // 자동으로 프로비저닝된 AuthGuard 사용
  @UseGuards(AuthGuard('local'))
  @Post('auth/login')
  async login(@Request() req) {
    // Passport의 validate() 메서드에서 반환된 값(user)이 Request 객체에 할당됨
    return req.user;
  }
}

// 전략이름을 AuthGuard()에 직접 전달하는 것 보다
// 별도의 클래스를 생성하는 것이 좋음
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

@UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Request() req) {
  return req.user;
}

2. JWT 발행

  • 인증 헤더의 전달자 토큰으로 전송할 수 있는 JWT 발행
@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService
  ) {}
  
  async validateUser(username: string, pass: string): Promise<any> {
	// 생략
  }
  
  async login(user: any) {
    const payload = { username: user.username, sub: user.userId };
    return {
      // JWT 발행
      access_token: this.jwtService.sign(payload),
    };
  }
}
  • JWT Secret Key
    - 키가 공개되면 안됨
    - 환경 변수와 같은 방법을 이용해 보호되어야 함
export const jwtConstants = {
  secret: 'secretKey',
};
@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: jwtConstants.secret,
      
      // 환경 변수 이용
      //secret: process.env.JWT_SECRET,
      
      signOptions: { expiresIn: '60s' },
    }),
  ],
  providers: [AuthService, LocalStrategy],
  exports: [AuthService, JwtModule],
})
export class AuthModule {}

@Controller()
export class AppController {
  constructor(private authService: AuthService) {}
  @UseGuards(LocalAuthGuard)
  @Post('auth/login')
  async login(@Request() req) {
    // 로그인할 경우 JWT 반환됨
    return this.authService.login(req.user);
  }
}

Implementing Passport JWT

  • 요청에 유효한 JWT를 요구하여 엔드 포인트 보호
// auth/jwt.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      // Request에서 JWT를 추출하는 방법 제공
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),	 
      
      // JWT가 Passport 모듈에 만료되었는지 확인 
      // 만료된 JWT가 전달될 경우, '401 Unauthorized' 자동 응답 처리
      ignoreExpiration: false, 
      
      // 토큰 서명을 위한 대칭 암호 제공
      // 공개하면 안됨
      secretOrKey: jwtConstants.secret,
    });
  }
  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

// auth/auth.module.ts
@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: jwtConstants.secret,
      signOptions: { expiresIn: '60s' },
    }),
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}
// auth/jwt-auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

3. 보호 경로와 관련 Guards 구현

: 유효한 JWT를 포함하는 요청에만 엑세스할 수 있는 보호 경로 생성

@Controller()
export class AppController {
  constructor(private authService: AuthService) {}
  @UseGuards(LocalAuthGuard)
  @Post('auth/login')
  async login(@Request() req) {
    return this.authService.login(req.user);
  }
  
  // 추가된 부분
  @UseGuards(JwtAuthGuard)
  @Get('profile')
  getProfile(@Request() req) {
    return req.user;
  }
}

-> GET/profile 접근 시,
Guards는 자동으로 passport-jwt 로직 호출하여 JWT 검증하고 user 속성을 Request 객체에 할당

profile
junior backend-developer 👶💻

0개의 댓글