MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

NestJS:Cách triển khai xác thực người dùng dựa trên phiên

Giới thiệu

Một thực tế không thể chối cãi rằng xác thực là rất quan trọng trong bất kỳ ứng dụng hoặc hệ thống nào nếu bạn muốn bảo mật dữ liệu người dùng và cho phép truy cập an toàn vào thông tin. Xác thực là thủ tục xác lập hoặc chứng minh rằng điều gì đó là đúng, hợp pháp hoặc hợp lệ.

Điều kiện tiên quyết

Hướng dẫn này là một minh chứng thực hành. Để làm theo, hãy đảm bảo bạn có những điều sau:

  • Node.js đang chạy trong hệ thống của bạn vì NestJS là một khung Node.js
  • MongoDB đã được cài đặt

NestJS là gì?

Nest (NestJS) là một khung ứng dụng phía máy chủ Node.js để xây dựng các ứng dụng hiệu quả, có thể mở rộng.

Nó được viết bằng TypeScript và được xây dựng trên Express, một khung công tác rất tối giản, tự nó tuyệt vời nhưng thiếu cấu trúc. Nó kết hợp các mô hình lập trình như lập trình hướng đối tượng, lập trình chức năng và lập trình phản ứng chức năng.

Nó là một khuôn khổ để sử dụng nếu bạn muốn có nhiều cấu trúc trên phần phụ trợ của mình. Cú pháp và cấu trúc của nó rất giống với AngularJS, một front-end framework. Và nó sử dụng TypeScript, dịch vụ và chèn phụ thuộc theo cách giống như AngularJS.

Nó sử dụng các mô-đun và bộ điều khiển, đồng thời bạn có thể tạo bộ điều khiển cho tệp bằng giao diện dòng lệnh.

Các mô-đun NestJS cho phép bạn nhóm các bộ điều khiển và nhà cung cấp dịch vụ có liên quan vào một tệp mã duy nhất. Nói một cách đơn giản, mô-đun NestJS là một tệp TypeScript có @Module chú thích (). Trình trang trí này thông báo cho khung NestJS về bộ điều khiển, nhà cung cấp dịch vụ và các tài nguyên liên quan khác sẽ được mã ứng dụng khởi tạo và sử dụng sau này.

Xác thực dựa trên phiên là gì?

Xác thực dựa trên phiên là một phương pháp xác thực người dùng, trong đó máy chủ tạo một phiên sau khi đăng nhập thành công, với ID phiên được lưu trữ trong cookie hoặc bộ nhớ cục bộ trong trình duyệt của bạn.

Theo các yêu cầu tiếp theo, cookie của bạn được xác thực dựa trên ID phiên được lưu trữ trên máy chủ. Nếu khớp, yêu cầu được coi là hợp lệ và được xử lý.

Khi sử dụng phương pháp xác thực này, điều quan trọng là phải ghi nhớ các phương pháp bảo mật tốt nhất sau:

  • Tạo ID phiên dài và ngẫu nhiên (128 bit là độ dài được khuyến nghị) để làm cho các cuộc tấn công brute force không hiệu quả
  • Tránh lưu trữ bất kỳ dữ liệu nhạy cảm hoặc dữ liệu người dùng cụ thể nào
  • Đặt giao tiếp HTTPS bắt buộc đối với tất cả các ứng dụng dựa trên phiên
  • Tạo cookie có các thuộc tính bảo mật và chỉ HTTP

Tại sao lại xác thực dựa trên phiên?

Xác thực dựa trên phiên an toàn hơn hầu hết các phương pháp xác thực vì nó đơn giản, an toàn và có kích thước lưu trữ hạn chế. Nó cũng được cho là lựa chọn tốt nhất cho các trang web trong cùng một miền gốc.

Thiết lập dự án

Bắt đầu thiết lập dự án của bạn bằng cách cài đặt Nest CLI trên toàn cầu. Bạn không cần phải làm điều này nếu bạn đã cài đặt NestJS CLI.

Nest CLI là một công cụ giao diện dòng lệnh để thiết lập, phát triển và duy trì các ứng dụng Nest.

npm i -g @nestjs/cli

Bây giờ, hãy thiết lập dự án của bạn bằng cách chạy lệnh sau:

nest new session-based-auth

Lệnh trên tạo ứng dụng Nest với một số bản soạn sẵn, sau đó nhắc bạn chọn trình quản lý gói ưa thích để cài đặt các mô-đun cần thiết để chạy ứng dụng của bạn. Để minh họa, hướng dẫn này sử dụng npm . Nhấn phím enter để tiếp tục với npm .

Nếu mọi thứ diễn ra tốt đẹp, bạn sẽ thấy kết quả như trên ảnh chụp màn hình bên dưới trên thiết bị đầu cuối của mình.

Sau khi cài đặt xong, hãy chuyển vào thư mục dự án của bạn và chạy ứng dụng bằng lệnh bên dưới:

npm run start:dev

Lệnh trên chạy ứng dụng và theo dõi các thay đổi. Dự án của bạn src cấu trúc thư mục sẽ như sau.

└───src
│   └───app.controller.ts
│   └───app.modules.ts
│   └───app.service.ts
│   └───main.ts

Cài đặt Phụ thuộc

Bây giờ ứng dụng của bạn đã được thiết lập, hãy cài đặt các phần phụ thuộc cần thiết.

npm install --save @nestjs/passport passport passport-local

Lệnh trên cài đặt Passport.js, một thư viện xác thực nest.js phổ biến.

Ngoài ra, hãy cài đặt các loại cho chiến lược bằng lệnh bên dưới:

Nó chứa các định nghĩa loại cho passport-local .

npm install --save-dev @types/passport-local

Thiết lập cơ sở dữ liệu MongoDB trong NestJS

Để thiết lập và kết nối cơ sở dữ liệu của bạn, hãy cài đặt gói Mongoose và trình bao bọc NestJS bằng lệnh sau:

npm install --save @nestjs/mongoose mongoose

Trình bao bọc Mongoose NestJS giúp bạn sử dụng Mongoose trong ứng dụng NestJS và cung cấp hỗ trợ TypeScript đã được phê duyệt.

Bây giờ, hãy truy cập vào app.module.ts của bạn và nhập mongoose mô-đun từ @nestjs/mongoose . Sau đó gọi forRoot() , một phương thức được cung cấp bởi mô-đun Mongoose và chuyển vào chuỗi URL cơ sở dữ liệu của bạn.

Thiết lập kết nối cơ sở dữ liệu của bạn trong app.module.ts giúp ứng dụng của bạn kết nối với cơ sở dữ liệu ngay lập tức khi máy chủ khởi động - sau khi chạy ứng dụng của bạn vì đây là mô-đun đầu tiên được tải.

app.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"

@Module({
  imports: [
    MongooseModule.forRoot(
      "mongodb+srv://<username>:<password>@cluster0.kngtf.mongodb.net/session-auth?retryWrites=true&w=majority"
    ),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Tạo mô-đun người dùng

Đối với các mối quan tâm về phân tách, để làm cho mã của bạn sạch sẽ và được tổ chức tốt, hãy tạo một mô-đun dành riêng cho người dùng sử dụng NestJS CLI bằng cách chạy lệnh sau:

nest g module users

Lệnh trên tạo một users thư mục có users.module.ts và các bản cập nhật app.module.ts

Ngoài ra, hãy tạo users.service.tsusers.controller.ts tệp với các lệnh sau:

nest g service users
nest g controller users

Lưu ý rằng bạn có thể tạo các thư mục và tệp của mình theo cách thủ công mà không cần sử dụng CLI lồng nhau, nhưng sử dụng CLI sẽ tự động cập nhật các thư mục cần thiết và giúp cuộc sống của bạn dễ dàng hơn.

Tạo lược đồ người dùng

Bước tiếp theo là tạo UserSchema của bạn, nhưng trước tiên, hãy thêm một users.model.ts tệp, nơi bạn sẽ tạo UserSchema

Đây phải là hình dạng của ứng dụng của chúng tôi src thư mục ngay bây giờ.

└───src
│   └───users
│   │   └───users.controller.ts
│   │   └───users.model.ts
│   │   └───users.module.ts
│   │   └───users.service.ts
│   └───app.controller.ts
│   └───app.module.ts
│   └───app.service.ts
│   └───main.ts

Để tạo UserSchema , nhập mọi thứ dưới dạng mongoose từ gói mongoose trong users.model.ts . Sau đó, gọi lược đồ mongoose mới, một bản thiết kế của Mô hình người dùng và chuyển vào một đối tượng JavaScript nơi bạn sẽ xác định đối tượng người dùng và dữ liệu.

users.model.ts

import * as mongoose from "mongoose"
export const UserSchema = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
  },
  { timestamps: true }
)

export interface User extends mongoose.Document {
  _id: string;
  username: string;
  password: string;
}

Ngoài ra, hãy tạo một giao diện cho Mô hình của bạn mở rộng mongoose, một tài liệu giúp bạn điền vào các bộ sưu tập MongoDB của mình.

Đi tới users.module.ts của bạn và nhập MongooseModule trong mảng nhập khẩu. Sau đó gọi forFeature() phương thức được cung cấp bởi MongooseModule và chuyển vào một mảng đối tượng có tên và lược đồ.

Điều này sẽ cho phép bạn chia sẻ tệp ở bất kỳ đâu với sự trợ giúp của chèn phụ thuộc.

users.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { UsersController } from "./users.controller"
import { UserSchema } from "./users.model"
import { UsersService } from "./users.service"
@Module({
  imports: [MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

Trong users.module.ts , xuất UsersService để cho phép bạn truy cập nó trong một mô-đun khác.

users.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { UsersController } from "./users.controller"
import { UserSchema } from "./users.model"
import { UsersService } from "./users.service"
@Module({
  imports: [MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

Thông thường, bạn nên đóng gói logic nghiệp vụ trong một lớp riêng biệt. Một lớp như vậy được gọi là một dịch vụ. Công việc của lớp này là xử lý các yêu cầu của bộ điều khiển và thực hiện logic nghiệp vụ.

Trong users.service.ts tệp, nhập Model từ mongoose , User từ users.model.tsInjectModel từ @nestjs/mongoose . Sau đó, thêm một phương thức vào UsersService lớp lấy tên người dùng và mật khẩu và gọi phương thức insertUser() .

users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './users.model';
@Injectable()
export class UsersService {
  constructor(@InjectModel('user') private readonly userModel: Model<User>) {}
  async insertUser(userName: string, password: string) {
    const username = userName.toLowerCase();
    const newUser = new this.userModel({
      username,
      password,
    });
    await newUser.save();
    return newUser;
  }
}

Bây giờ UsersService lớp đã sẵn sàng, bạn cần phải đưa nó vào bộ điều khiển của mình. Nhưng trước tiên, hãy nói về việc lưu trữ mật khẩu của người dùng một cách an toàn.

Khía cạnh quan trọng nhất của thủ tục đăng ký là mật khẩu của người dùng, mật khẩu này không được lưu dưới dạng văn bản thuần túy. Người dùng có trách nhiệm tạo một mật khẩu mạnh, nhưng với tư cách là nhà phát triển, bạn có nghĩa vụ phải giữ an toàn cho mật khẩu của họ. Nếu vi phạm cơ sở dữ liệu xảy ra, mật khẩu của người dùng sẽ bị lộ. Và điều gì sẽ xảy ra nếu nó được lưu trữ dưới dạng văn bản thuần túy? Tôi tin rằng bạn biết câu trả lời. Để giải quyết vấn đề này, hãy băm mật khẩu bằng bcrypt.

Vì vậy, hãy cài đặt bcrypt@types/bcrypt bằng lệnh sau:

npm install @types/bcrypt bcrypt

Với điều đó, hãy thiết lập bộ điều khiển của bạn. Đầu tiên, nhập UsersService của bạn lớp và mọi thứ từ bcrypt . Sau đó, thêm một phương thức khởi tạo và một phương thức cho phép bạn thêm một người dùng; nó sẽ xử lý các yêu cầu bài đến, gọi nó là addUser , với một phần thân hàm nơi bạn sẽ băm mật khẩu.

users.controller.ts

import { Body, Controller, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import * as bcrypt from 'bcrypt';
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
  //post / signup
  @Post('/signup')
  async addUser(
    @Body('password') userPassword: string,
    @Body('username') userName: string,
  ) {
    const saltOrRounds = 10;
    const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
    const result = await this.usersService.insertUser(
      userName,
      hashedPassword,
    );
    return {
      msg: 'User successfully registered',
      userId: result.id,
      userName: result.username
    };
  }
}

Việc đăng ký diễn ra trong app.module.ts , đạt được bằng cách thêm UsersModule tới @Module() mảng nhập khẩu của decorator trong app.module.ts .

app.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"
import { UsersModule } from "./users/users.module"

@Module({
  imports: [
    MongooseModule.forRoot(
      "mongodb+srv://<username>:<password>@cluster0.kngtf.mongodb.net/session-auth?retryWrites=true&w=majority"
    ),
    UsersModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Xin chúc mừng! Bạn đã hoàn tất việc đăng ký. Bây giờ bạn có thể đăng ký người dùng bằng tên người dùng và mật khẩu.

Bây giờ, không cần đăng ký, hãy thêm getUser chức năng cho UsersService của bạn với findOne phương pháp tìm người dùng theo tên người dùng.

users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './users.model';
@Injectable()
export class UsersService {
  constructor(@InjectModel('user') private readonly userModel: Model<User>) {}
  async insertUser(userName: string, password: string) {
    const username = userName.toLowerCase();
    const newUser = new this.userModel({
      username,
      password,
    });
    await newUser.save();
    return newUser;
  }
  async getUser(userName: string) {
    const username = userName.toLowerCase();
    const user = await this.userModel.findOne({ username });
    return user;
  }
}

Tạo mô-đun xác thực

Cũng như đối với người dùng, hãy tạo mô-đun xác thực và dịch vụ dành riêng cho tất cả các xác thực / xác minh. Để làm điều đó, hãy chạy các lệnh sau:

nest g module auth
nest g service auth

Ở trên sẽ tạo một thư mục auth, auth.module.tsauth.service.ts và cập nhật auth.module.tsapp.module.ts tệp.

Tại thời điểm này, hình dạng ứng dụng của bạn src thư mục sẽ trông như sau.

└───src
│   └───auth
│   │   └───auth.module.ts
│   │   └───auth.service.ts
│   └───users
│   │   └───users.controller.ts
│   │   └───users.model.ts
│   │   └───users.module.ts
│   │   └───users.service.ts
│   └───app.controller.ts
│   └───app.module.ts
│   └───app.service.ts
│   └───main.ts

Lệnh tạo ở trên sẽ cập nhật app.module.ts của bạn và nó sẽ giống như đoạn mã bên dưới:

app.module.ts

import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"
import { UsersModule } from "./users/users.module"
import { AuthModule } from './auth/auth.module';


@Module({
  imports: [UsersModule, AuthModule, MongooseModule.forRoot(
    //database url string
    'mongodb://localhost:27017/myapp'
    )],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Xác thực người dùng

Truy cập auth.module.ts của bạn tệp và thêm UsersModule trong mảng nhập để cho phép truy cập vào UsersService được xuất từ ​​users.module.ts tệp.

auth.module.ts

import { Module } from "@nestjs/common"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"

@Module({
  imports: [UsersModule],
  providers: [AuthService],
})
export class AuthModule {}

Trong auth.service.ts của bạn tệp, gọi hàm tạo để bạn có thể đưa vào UsersService và thêm một phương thức xác thực sẽ sử dụng tên người dùng và mật khẩu.

Để thêm một số xác thực cơ bản, hãy kiểm tra xem người dùng có tồn tại trong cơ sở dữ liệu hay không và so sánh mật khẩu đã cho với mật khẩu trong cơ sở dữ liệu của bạn để đảm bảo nó khớp. Nếu nó tồn tại, hãy trả lại người dùng trong request.user object - else, trả về null.

auth.service.ts

    import { Injectable, NotAcceptableException } from '@nestjs/common';
    import { UsersService } from 'src/users/users.service';
    import * as bcrypt from 'bcrypt';

    @Injectable()
    export class AuthService {
      constructor(private readonly usersService: UsersService) {}
      async validateUser(username: string, password: string): Promise<any> {
        const user = await this.usersService.getUser(username);
        const passwordValid = await bcrypt.compare(password, user.password)
        if (!user) {
            throw new NotAcceptableException('could not find the user');
          }
        if (user && passwordValid) {
          return {
            userId: user.id,
            userName: user.username
          };
        }
        return null;
      }
    }

Đi xa hơn, hãy tạo một tệp mới và đặt tên là local.strategy.ts . Tệp này sẽ đại diện cho chiến lược từ Passport.js , mà bạn đã cài đặt trước đó, đó là local strategy . Và bên trong nó, hãy vượt qua chiến lược, đó là Strategy từ passport-local .

Tạo một hàm tạo và đưa vào AuthService , gọi super() phương pháp; đảm bảo gọi super() phương pháp.

local.strategy.ts

    import { Injectable, UnauthorizedException } from '@nestjs/common';
    import { PassportStrategy } from '@nestjs/passport';
    import { Strategy } from 'passport-local';
    import { AuthService } from './auth.service';
    @Injectable()
    export class LocalStrategy extends PassportStrategy(Strategy) {
      constructor(private readonly authService: AuthService) {
        super();
      }
      async validate(username: string, password: string): Promise<any> {
        const userName = username.toLowerCase();
        const user = await this.authService.validateUser(userName, password);
        if (!user) {
          throw new UnauthorizedException();
        }
        return user;
      }
    }

Quay lại auth.module.ts của bạn tập tin. Sau đó thêm PassportModule để nhập và LocalStrategy cho các nhà cung cấp.

auth.module.ts

import { Module } from "@nestjs/common"
import { PassportModule } from "@nestjs/passport"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"
import { LocalStrategy } from "./local.strategy"

@Module({
  imports: [UsersModule, PassportModule],
  providers: [AuthService, LocalStrategy],
})
export class AuthModule {}

Bây giờ, hãy thêm tuyến đăng nhập vào users.controller.ts của bạn :

users.controller.ts

    import {
      Body,
      Controller,
      Post,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //post / signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
    }

Bây giờ bạn đã có tất cả những thứ này, bạn vẫn không thể đăng nhập một người dùng vì không có gì để kích hoạt lộ trình đăng nhập. Tại đây, hãy sử dụng Vệ binh để đạt được điều đó.

Tạo tệp và đặt tên là local.auth.guard.ts , sau đó là một lớp LocalAuthGuard mở rộng AuthGuard từ NestJS/passport , nơi bạn sẽ cung cấp tên của chiến lược và chuyển vào tên chiến lược của bạn, local .

local.auth.guard.ts.

import { Injectable } from "@nestjs/common"
import { AuthGuard } from "@nestjs/passport"
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}

Thêm UseGuard trang trí cho tuyến đường đăng nhập của bạn trong users.controller.ts và chuyển vào LocalAuthGuard .

users.controller.ts

    import {
      Body,
      Controller,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //post / signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
    }

Cuối cùng, bạn có thể đăng nhập một người dùng bằng tên người dùng và mật khẩu đã đăng ký.

Bảo vệ các tuyến đường xác thực

Bạn đã thiết lập thành công xác thực người dùng. Bây giờ, hãy bảo vệ các tuyến đường của bạn khỏi bị truy cập trái phép bằng cách giới hạn quyền truy cập đối với những người dùng đã được xác thực. Truy cập users.controller.ts của bạn và thêm một tuyến đường khác - đặt tên là ‘protected’ và đặt nó trả về req.user đối tượng.

users.controller.ts

    import {
      Body,
      Controller,
      Get,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
    // Get / protected
      @Get('/protected')
      getHello(@Request() req): string {
        return req.user;
      }
    }

Tuyến được bảo vệ trong đoạn mã trên sẽ trả về một đối tượng trống thay vì trả về thông tin chi tiết của người dùng khi người dùng đã đăng nhập yêu cầu nó vì nó đã mất thông tin đăng nhập.

Để sắp xếp điều đó, đây là nơi xác thực dựa trên phiên hoạt động.

Trong xác thực dựa trên phiên, khi người dùng đăng nhập, người dùng được lưu trong một phiên để mọi yêu cầu tiếp theo của người dùng sau khi đăng nhập sẽ lấy thông tin chi tiết từ phiên và cấp cho người dùng dễ dàng truy cập. Phiên hết hạn khi người dùng đăng xuất.

Để bắt đầu xác thực dựa trên phiên, hãy cài đặt express-session và các loại NestJS bằng lệnh sau:

npm install express-session @types/express-session

Khi quá trình cài đặt hoàn tất, hãy truy cập main.ts của bạn tệp, thư mục gốc của ứng dụng của bạn và thực hiện các cấu hình ở đó.

Nhập mọi thứ từ passportexpress-session , sau đó thêm phiên khởi tạo hộ chiếu và phiên hộ chiếu.

Tốt hơn là giữ khóa bí mật của bạn trong các biến môi trường của bạn.

main.ts

import { NestFactory } from "@nestjs/core"
import { AppModule } from "./app.module"
import * as session from "express-session"
import * as passport from "passport"
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.use(
    session({
      secret: "keyboard",
      resave: false,
      saveUninitialized: false,
    })
  )
  app.use(passport.initialize())
  app.use(passport.session())

  await app.listen(3000)
}
bootstrap()

Thêm một tệp mới, authenticated.guard.ts , trong auth của bạn thư mục. Và tạo một Guard mới để kiểm tra xem có phiên nào cho người dùng thực hiện yêu cầu hay không - đặt tên cho nó là authenticatedGuard .

authenticated.guard.ts

import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"

@Injectable()
export class AuthenticatedGuard implements CanActivate {
  async canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest()
    return request.isAuthenticated()
  }
}

Trong đoạn mã trên, yêu cầu được lấy từ ngữ cảnh và được kiểm tra xem đã được xác thực chưa. isAuthenticated() đến từ passport.js tự động; nó nói rằng. "này! có phiên nào tồn tại cho người dùng này không? Nếu vậy, hãy tiếp tục."

Để kích hoạt đăng nhập, trong users.controller.ts của bạn tệp:

  • nhập authenticated từ authenticated.guard.ts;
  • thêm useGuard trang trí cho protected tuyến đường; và,
  • chuyển vào AuthenticatedGuard .

users.controller.ts

    import {
      Body,
      Controller,
      Get,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { AuthenticatedGuard } from 'src/auth/authenticated.guard';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
      //Get / protected
      @UseGuards(AuthenticatedGuard)
      @Get('/protected')
      getHello(@Request() req): string {
        return req.user;
      }
    }

Tại thời điểm này, nó vẫn không thành công vì bạn chỉ định cấu hình express-session nhưng không triển khai nó.

Khi người dùng đăng nhập, bạn cần lưu người dùng trong một phiên để người dùng có thể truy cập các tuyến đường khác với phiên đó.

Một điều cần lưu ý là theo mặc định, express-session thư viện lưu trữ phiên trong bộ nhớ của máy chủ web.

Trước khi nó đi vào phiên, bạn cần phải tuần tự hóa người dùng. Khi nó ra khỏi phiên, hãy deserialize người dùng.

Vì vậy, hãy tạo một tệp mới trong thư mục auth cho serializer và deserializer, đặt tên là session.serializer.ts .

Tại thời điểm này, hình dạng của ứng dụng của chúng ta src thư mục sẽ giống như thế này.

    └───src
    │   └───auth
    │   │   └───auth.module.ts
    │   │   └───auth.service.ts
    │   │   └───authenticated.guard.ts
    │   │   └───local.auth.guard.ts
    │   │   └───local.strategy.ts
    │   │   └───session.serializer.ts
    │   └───users
    │   │   └───users.controller.ts
    │   │   └───users.model.ts
    │   │   └───users.module.ts
    │   │   └───users.service.ts
    │   └───app.controller.ts
    │   └───app.module.ts
    │   └───app.service.ts
    │   └───main.ts

session.serializer.ts

import { Injectable } from "@nestjs/common"
import { PassportSerializer } from "@nestjs/passport"

@Injectable()
export class SessionSerializer extends PassportSerializer {
  serializeUser(user: any, done: (err: Error, user: any) => void): any {
    done(null, user)
  }
  deserializeUser(
    payload: any,
    done: (err: Error, payload: string) => void
  ): any {
    done(null, payload)
  }
}

Quay lại auth.module.ts của bạn , cung cấp SessionSerializer và thêm register đến PassportModule .

auth.module.ts

import { Module } from "@nestjs/common"
import { PassportModule } from "@nestjs/passport"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"
import { LocalStrategy } from "./local.strategy"
import { SessionSerializer } from "./session.serializer"

@Module({
  imports: [UsersModule, PassportModule.register({ session: true })],
  providers: [AuthService, LocalStrategy, SessionSerializer],
})
export class AuthModule {}

Add some codes within the LocalAuthGuard in the local.auth.guard.ts tệp.

Call the login method in super and pass in the request to trigger the actual login by creating a session. If you want to use sessions, you must remember to trigger the super.login() .

local.auth.guard.ts

    import { ExecutionContext, Injectable } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    @Injectable()
    export class LocalAuthGuard extends AuthGuard('local') {
      async canActivate(context: ExecutionContext) {
        const result = (await super.canActivate(context)) as boolean;
        const request = context.switchToHttp().getRequest();
        await super.logIn(request);
        return result;
      }
    }

If you log in now, you will see the session ID stored in a cookie, which is just a key to the session store, and the cookie gets saved in the browser. The cookie is automatically attached to the rest of the request.

Now that the session is working, you can access the protected route; it will return the expected user’s details.

Logout Users

As mentioned earlier, once a user logs out, you destroy all sessions.

To log out a user, go to the users.controller.ts file, add a logout route, and call the req.session.session() phương pháp. You can return a message notifying that the user’s session has ended.

    import {
      Body,
      Controller,
      Get,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { AuthenticatedGuard } from 'src/auth/authenticated.guard';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
       //Get / protected
      @UseGuards(AuthenticatedGuard)
      @Get('/protected')
      getHello(@Request() req): string {
        return req.user;
      }
       //Get / logout
      @Get('/logout')
        logout(@Request() req): any {
          req.session.destroy();
          return { msg: 'The user session has ended' }
        }
    }

So, once you log out, it returns a message notifying you that the user session has ended. The code for this tutorial is hosted here on my Github repository.

Test Your Application

You have successfully implemented user signup, authentication, and protected the route to enable authorized access only.

It’s time to test the application. If everything is in order, your server should be running. Else, restart your server with the following command:

npm run start:dev

Head over to your Postman. And let’s finally test our application.

Sign Up As a User

Log In As a User

Request the Protected Route

User Logout

Alternatively, Implement User Authentication with LoginRadius

LoginRadius provides a variety of registration and authentication services to assist you in better connecting with your consumers.

On any web or mobile application, LoginRadius is the developer-friendly Identity Platform that delivers a complete set of APIs for authentication, identity verification, single sign-on, user management, and account protection capabilities like multi-factor authentication.

To implement LoginRadius in your NestJS application, follow this tutorial:NestJS User Authentication with LoginRadius API.

Conclusion

Xin chúc mừng! In this tutorial, you've learned how to implement session-based authentication in a NestJS application with the MongoDB database. You've created and authenticated a user and protected your routes from unauthorized access.

You can access the sample code used in this tutorial on GitHub.

Lưu ý: Session storage is saved by default in 'MemoryStore,' which is not intended for production use. So, while no external datastore is required for development, once in production, a data store such as Redis or another is suggested for stability and performance. You can learn more about session storage here.


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Các mảng lồng nhau trong Mongoose

  2. Cách tạo API RESTful tốt nhất trong Node.js

  3. Cách sử dụng Mongoose Promise - mongo

  4. Tại sao tôi nhận được cảnh báo không dùng nữa này ?! MongoDB

  5. Không thể kết nối với Cơ sở dữ liệu mongodb Mongo Cloud trong Golang trên Ubuntu