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.ts
và users.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.ts
và InjectModel
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
và @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.ts
và auth.service.ts
và cập nhật auth.module.ts
và app.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ừ passport
và express-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í choprotected
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
Logged-in User’s Cookie ID
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.