Xác thực người dùng trong Nestjs sử dụng Passport JWT

Chào mừng các bạn trở lại với series tutorial Nestjs của mình. Ở bài trước mình đã giới thiệu về setup repository + typeorm tại đây. Để tiếp tục series mình cùng các bạn sẽ tìm hiểu vể JWT trong Nestjs sử dụng Passportjs. Bắt đầu nhé 1. Giới thiệu Json Web Token (JWT) là

Chào mừng các bạn trở lại với series tutorial Nestjs của mình. Ở bài trước mình đã giới thiệu về setup repository + typeorm tại đây. Để tiếp tục series mình cùng các bạn sẽ tìm hiểu vể JWT trong Nestjs sử dụng Passportjs. Bắt đầu nhé

1. Giới thiệu

Json Web Token (JWT) là một tiêu chuẩn mở định nghĩa một cách nhỏ gọn và khép kín để truyền thông tin an toàn giữa các bên dưới dạng đối tượng JSON. Thông tin này có thể được xác minh và đáng tin cậy vì nó được ký điện tử. JWT có thể được ký bằng cách sử dụng bí mật (với thuật toán HMAC ) hoặc cặp khóa công khai / riêng tư bằng RSA hoặc ECDSA.

JWT cũng hỗ trợ cho rất nhiều ngôn ngữ và framework khác nhau như: python, php, laravel, .Net … đặc biệt trong đó có cả Nestjs. Hiện nay khi nhắc đến xác thực cho Web API người ta cũng thường nhắc đến JWT.

2. Cài đặt các packet, directory stucture, migration User

  • Trước tên chúng ta cần install các packege sau:

    > npm i bcrypt
    
          > npm install class-validator
    
          > npm i @nestjs/passport passport passport-local
    
          > nest i @nestjs/jwt passport-jwt 
    
  • Tiếp đến là folder structure của dự án:

    src/
    ├──  auth/
    │   ├── dto/
    │   │   └── create-user.dto.ts
    │   ├── guards/
    │   │   ├── auth.guard.ts
    │   │   └── local.guards.ts
    │   ├── interfaces/
    │   │   └── auth-payload.interface.ts
    │   ├── strategies
    │   │   ├── jwt.strategy.ts
    │   │   └── local.strategy.ts
    │   ├── auth.controller.ts
    │   ├── auth.module.ts
    │   └── auth.service.ts
    ├── databse/
    │   ├── migrations/
    │   │   └── 123456789-userTable.ts
    └── models/
       ├── users/
       │   ├── interfaces/
       │   │   └── user.interface.ts
       │   ├── entities/
       │   │   └── user.entity.ts
       │   ├── serializers/
       │   │   └── user.serializer.ts
       │   ├── user.controller.ts
       │   ├── user.module.ts
       │   ├── user.repository.ts
       │   └── user.service.ts
       ├── model.repository.ts
       └── model.serializer.ts
    
  • tạo migration for users table

    import{ MigrationInterface, QueryRunner, Table }from'typeorm';exportclasscreateUserTable1633225887055implementsMigrationInterface{publicasyncup(queryRunner: QueryRunner): Promise<void>{await queryRunner.createTable(newTable({
            name:'users',
            columns:[{
                name:'id',
                type:'bigint',
                isPrimary:true,
                isGenerated:true,
                generationStrategy:'increment',},{
                name:'email',
                type:'varchar',
                isNullable:true,},...............// các cloumn tùy các bạn define{
                name:'password',
                type:'varchar',
                isNullable:true,},{
                name:'created_at',
                type:'timestamp',
                isNullable:true,default:'now()',},{
                name:'updated_at',
                type:'timestamp',
                isNullable:true,default:'now()',},],}),);}publicasyncdown(queryRunner: QueryRunner): Promise<void>{await queryRunner.dropTable('users');}}

3. Định nghĩa UserService và Validation cho chức năng tạo mới user

  • Đầu tiên là models/user.repository.ts

    import{ EntityRepository }from'typeorm';...
    
          @EntityRepository(User)exportclassUsersRepositoryextendsModelRepository<User, UserEntity>/**
             * function get user by email
             */asyncgetUserByEmail(email: string): Promise<UserEntity>{returnawaitthis.findOne({
                where:{ email: email },}).then((entity)=>{if(!entity){return Promise.reject(newNotFoundException('Model not found'));}return Promise.resolve(entity ?this.transform(entity):null);});}...... bài trước mình đã có hướng dẫn cụ thể các bạn có thể tham khảo lại
          }
  • tiếp theo là models/user.service.ts

    import{ Injectable }from'@nestjs/common';....
    
          @Injectable()exportclassUsersService{constructor(@InjectRepository(UsersRepository)private usersRepository: UsersRepository,){}.....asynccreate(inputs: CreateUserDto): Promise<UserEntity>{// nếu đang thắc mắc là trên userRepository không có function createEntity?// đừng lo vì nó đã được mình định nghĩa trong modelRepository rồi returnawaitthis.usersRepository.createEntity(inputs);}asyncgetUserByEmail(email: string): Promise<UserEntity>{returnawaitthis.usersRepository.getUserByEmail(email);}}
  • tiếp đến để sử dụng userService ở các module khác thì ta cần export nó trong models/user.module.ts

    import...
    
        @Module({
          imports:[ConfigModule, TypeOrmModule.forFeature([UsersRepository])],
          controllers:[UsersController],
          providers:[UsersService, ConfigModule, JsonWebTokenStrategy],
          exports:[UsersService],})
  • Tiếp đến chúng ta cần validation cho function create user auth/create-user.dto.ts

    import{ IsEnum,...}from'class-validator';exportclassCreateUserDto{
      @IsString()
      @MaxLength(255)
      @IsNotEmpty()
      name: string;
    
      @IsEmail()
      email: string;
    
      @IsNotEmpty()
      password: string;}
  • function create user auth/auth.controller.ts

    import{ Post }from'@nestjs/common';import{ CreateUserDto }from'./dto/CreateUser.dto';
    
    @Controller()exportclassAuthController{constructor(private userService: UsersService,private authService: AuthService,){}//fucntion register user
      @Post('/register')asyncregisterUser(@Body() input: CreateUserDto){const check =awaitthis.validate(input.email);if(!check){thrownewHttpException({ message:'User already exists'},
            HttpStatus.BAD_REQUEST,);}
    
        input.password =awaitthis.authService.hashPassword(input.password);returnthis.userService.create(input);}//handle login
      @UseGuards(LocalAuthGuard)
      @Post('/login')asynclogin(@Request() request): Promise<any>{returnthis.authService.login(request.user);}
      
      @UseGuards(AuthenticationGuard)
      @Get('users/:id')asyncgetUserById(@Param() params): Promise<UserEntity>{const user =awaitthis.usersService.findById(params.id);this.throwUserNotFound(user);return user;}//check user exists by emailasyncvalidate(email: string){try{const users =awaitthis.userService.geUsersByEmail(email);return users.length <=0;}catch(e){returnfalse;}}}
    • tiếp đến để sử dụng userService ta cần import nó trong models/auth.module.ts
    import...
    
        @Module({
          imports:[
            UsersModule,
            TypeOrmModule.forFeature([UsersRepository]),],
          providers:[AuthService, UsersService],
          controllers:[AuthController],})

4. Authentication & authorization

  • Định nghĩa auth/interfaces/auth-payload.interface.ts đây sẽ là định nghĩa các thông tin sẽ được lưu trong payload của JWT
    exportinterfaceAuthPayload{
          id: number | string;
          name:null| string;
          email: string;}
  • tiếp đến chúng ta sẽ cần định nghĩa auth/auth.service.ts
    import{ Injectable }from'@nestjs/common';...
    
    @Injectable()exportclassAuthService{constructor(private jwtService: JwtService,private userService: UsersService,){}//function hash passwordasynchashPassword(password: string): Promise<string>{returnawait bcrypt.hash(password,12);}//function compare password param with user password in databaseasynccomparePassword(
        password: string,
        storePasswordHash: string,): Promise<any>{returnawait bcrypt.compare(password, storePasswordHash);}asyncauthentication(email: string, password: string): Promise<any>{const user =awaitthis.userService.getUserByEmail(email);const check =awaitthis.comparePassword(password, user.password);if(!user ||!check){returnfalse;}return user;}asynclogin(user: UserEntity){const payload: AuthPayload ={
          name: user.name,
          email: user.email,
          id: user.id,};return{ access_token:this.jwtService.sign(payload)};}}
  • Định nghĩa auth/strategies/local.strategy.ts
    import{ PassportStrategy }from'@nestjs/passport';...
        
        @Injectable()exportclassLocalStrategyextendsPassportStrategy(Strategy){constructor(private authService: AuthService){super({ usernameField:'email'});// ở đây mình đăng nhập bằng email và password nên mình phải thực hiện custom usernameField}asyncvalidate(email: string, password: string): Promise<UserEntity>{const user =awaitthis.authService.authentication(email, password);if(!user){thrownewUnauthorizedException();}return user;}}
  • Định nghĩa auth/strategies/jwt.strategy.ts
    import{ PassportStrategy }from'@nestjs/passport';...
        
        @Injectable()exportclassJsonWebTokenStrategyextendsPassportStrategy(Strategy){constructor(){super({
              jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
              ignoreExpiration:false,
              secretOrKey:'JWT_SECRET_KEY',});}asyncvalidate(payload: AuthPayload){return{ name: payload.name, email: payload.email, id: payload.id };}}
  • Định nghĩa auth/guards/auth.guard.ts
    import{ AuthGuard }from'@nestjs/passport';import{ Injectable }from'@nestjs/common';
    
        @Injectable()exportclassAuthenticationGuardextendsAuthGuard('jwt'){}
  • Định nghĩa auth/guards/local.guard.ts
    import{ AuthGuard }from'@nestjs/passport';import{ Injectable }from'@nestjs/common';
    
        @Injectable()exportclassLocalAuthGuardextendsAuthGuard('local'){}
  • Cuối cùng ta cần update auth/auth.module.ts
    import{ Module }from'@nestjs/common';...
       @Module({
         imports:[
           UsersModule,
           TypeOrmModule.forFeature([UsersRepository]),
           PassportModule,
           JwtModule.register({
             secret:'JWT_SECRET_KEY',
             signOptions:{ expiresIn:'60m'},}),],
         providers:[AuthService, UsersService, LocalStrategy, JsonWebTokenStrategy],
         controllers:[AuthController],})exportclassAuthModule{}

5. Giải thích về request lifecycle

  • function login: người dùng gửi yêu cầu -> [email protected] -> gửi callback đến -> [email protected] -> nếu respose là user thì sẽ trả về token, nếu false sẽ throw error
  • function get UserById: người dùng gửi yêu cầu “localhost/user/{id} ” -> [email protected]> gửi callback đến -> [email protected] (check token) -> nếu respose là user thì sẽ trả về user by id, nếu false sẽ throw error

6. Kết luận

  • Đợt này dự án mình cũng hơi nhiều việc nên ra bài hướng dẫn khác chậm. Hy vọng bài viết này sẽ có ích cho các bạn. Nếu có bất kì thắc mắc hãy câu hỏi có thể đặt ở bên dưới.
  • Trong phần tiếp theo mình sẽ giới thiệu relationship trong Nestjs + typeOrm, rất mong được các bạn ủng hộ.
  • Bạn cũng có thể tham khảo của mình Repository : Tại đây

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ