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

Sự Khác Nhau Giữa Domain và Hosting Là Gì?

Sự khác nhau giữa domain và hosting là gì? Bài này giải thích ngắn và dễ hiểu nh

Shared Hosting hay VPS Hosting: Lựa chọn nào dành cho bạn?

Bài viết giải thích rõ shared hosting và vps hosting là gì và hướng dẫn chọn lựa

Thay đổi Package Name của Android Studio dể dàng với plugin APR

Nếu bạn đang gặp khó khăn hoặc bế tắc trong việc thay đổi package name trong And

Lỗi không Update Meta_Value Khi thay thế hình ảnh cũ bằng hình ảnh mới trong WordPress

Mã dưới đây hoạt động tốt có 1 lỗi không update được postmeta ” meta_key=