Hướng Dẫn Từng Bước Thêm Hệ Thống Người Dùng vào Nest.js
Trong bài hướng dẫn trước, chúng ta đã xây dựng một blog cơ bản hỗ trợ tạo bài viết. Tuy nhiên, một vấn đề lớn là blog này không có cơ chế xác thực, nghĩa là bất kỳ ai cũng có thể tạo hoặc xóa bài viết. Để khắc phục điều này, trong bài viết này, chúng ta sẽ thêm một hệ thống người dùng và xác thực cho blog.
Giới Thiệu Về Các Phương Thức Xác Thực
Trong phát triển web, có hai phương thức xác thực phổ biến nhất là Dựa trên Token (ví dụ: JWT) và Dựa trên Session (Cookie).
- JWT (JSON Web Tokens): Đây là phương thức xác thực phổ biến nhất hiện nay. Sau khi người dùng đăng nhập, máy chủ tạo một token và trả về cho client. Client sẽ gửi token này trong các yêu cầu tiếp theo và máy chủ chỉ cần xác minh token chưa bị thay đổi. Phương thức này rất thích hợp cho các trang web lớn với nhiều máy chủ, vì nó tránh việc phải đồng bộ hóa trạng thái người dùng liên tục giữa các máy chủ.
- Session-Cookie: Sau khi người dùng đăng nhập, máy chủ tạo một session và trả về Session ID cho trình duyệt qua cookie. Trình duyệt tự động gửi cookie này trong các yêu cầu tiếp theo. Máy chủ sẽ sử dụng Session ID trong cookie để tìm thông tin session tương ứng đã lưu trữ, từ đó xác định người dùng.
Trong bài hướng dẫn này, chúng ta sẽ chọn phương thức Session-Cookie. Blog của chúng ta là một ứng dụng đơn giản với kiến trúc truyền thống, vì vậy việc sử dụng Session-Cookie cho xác thực là cách tiếp cận trực tiếp, cổ điển và an toàn nhất. Nest.js cũng hỗ trợ rất tốt cho phương thức này.
Tạo Module Người Dùng
Trước khi xử lý xác thực, chúng ta hãy thêm một hệ thống người dùng trước.
Tương tự như việc tạo module posts trong bài hướng dẫn trước, hãy sử dụng Nest CLI để nhanh chóng tạo các tệp cần thiết:
nest generate module users
nest generate controller users
nest generate service users
Tiếp theo, tạo một tệp thực thể người dùng user.entity.ts trong thư mục src/users để ánh xạ đến bảng users trong cơ sở dữ liệu.
typescript
// src/users/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
username: string;
@Column()
password: string; // Mật khẩu sẽ được mã hóa
}
Trong UsersModule (src/users/users.module.ts), đăng ký TypeOrmModule để nó có thể hoạt động trên thực thể User. Lưu ý rằng bạn cần xuất UsersService để có thể sử dụng trong các module khác sau này (như module Auth).
typescript
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // Xuất UsersService để sử dụng trong các module khác
})
export class UsersModule {}
Cuối cùng, thực hiện câu lệnh SQL sau trong cơ sở dữ liệu PostgreSQL mà chúng ta đã tạo trong bài hướng dẫn trước để tạo bảng user:
CREATE TABLE "user" (
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
"username" VARCHAR UNIQUE NOT NULL,
"password" VARCHAR NOT NULL
);
Nếu cơ sở dữ liệu của bạn được tạo trên Leapcell,
bạn có thể dễ dàng thực thi các câu lệnh SQL bằng giao diện đồ họa. Chỉ cần vào trang quản lý cơ sở dữ liệu trên website, dán câu lệnh ở trên vào giao diện SQL và chạy nó.
Thực Hiện Đăng Ký Người Dùng
Chỉnh sửa src/users/users.service.ts để thêm logic cho việc tạo và tìm người dùng.
Lưu ý: Để đảm bảo an toàn, mật khẩu của người dùng cần được mã hóa trước khi lưu trữ trong cơ sở dữ liệu. Chúng ta sẽ sử dụng thư viện bcrypt để mã hóa mật khẩu.
Cài đặt các phụ thuộc cần thiết:
npm install bcrypt
npm install -D @types/bcrypt
typescript
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>
) {}
async create(user: Partial<User>): Promise<User> {
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(user.password || '', saltRounds);
const newUser = this.usersRepository.create({
username: user.username,
password: hashedPassword,
});
return this.usersRepository.save(newUser);
}
async findOne(username: string): Promise<User | null> {
return this.usersRepository.findOneBy({ username });
}
}
Tạo Module Xác Thực và Logic Xác Nhận Đăng Nhập
Chúng ta sẽ đặt tất cả logic liên quan đến xác thực trong một module auth riêng biệt. Đầu tiên, chúng ta sẽ chỉ thực hiện logic cốt lõi "xác thực người dùng".
Sử dụng CLI để tạo module auth và service:
nest generate module auth
nest generate service auth
Chỉnh sửa src/auth/auth.service.ts để thêm phương thức validateUser xác minh xem tên người dùng và mật khẩu có đúng hay không.
typescript
// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
@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 && (await bcrypt.compare(pass, user.password))) {
const { password, ...result } = user;
return result; // Xác thực thành công, trả về thông tin người dùng không có mật khẩu
}
return null; // Xác thực thất bại
}
}
Bây giờ, nhập UsersModule vào AuthModule để AuthService có thể sử dụng UsersService.
typescript
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
Tạo Trang Đăng Nhập và Đăng Ký
Chúng ta cần cung cấp một giao diện để người dùng tương tác. Trong thư mục views, tạo login.ejs và register.ejs.
register.ejs
html
<%- include('_header', { title: 'Đăng Ký' }) %>
<form action="/users/register" method="POST" class="post-form">
<h2>Đăng Ký</h2>
<div class="form-group">
<label for="username">Tên người dùng</label>
<input type="text" id="username" name="username" required />
</div>
<div class="form-group">
<label for="password">Mật khẩu</label>
<input type="password" id="password" name="password" required />
</div>
<button type="submit">Đăng Ký</button>
</form>
<%- include('_footer') %>
login.ejs
html
<%- include('_header', { title: 'Đăng Nhập' }) %>
<form action="/auth/login" method="POST" class="post-form">
<h2>Đăng Nhập</h2>
<div class="form-group">
<label for="username">Tên người dùng</label>
<input type="text" id="username" name="username" required />
</div>
<div class="form-group">
<label for="password">Mật khẩu</label>
<input type="password" id="password" name="password" required />
</div>
<button type="submit">Đăng Nhập</button>
</form>
<%- include('_footer') %>
Thực Hiện Routing và Logic Controller
Bây giờ, chúng ta sẽ tạo các route để xử lý yêu cầu đăng ký và đăng nhập.
Route Đăng Ký
Cập nhật src/users/users.controller.ts để xử lý hiển thị trang đăng ký và gửi biểu mẫu:
typescript
// src/users/users.controller.ts
import { Controller, Get, Post, Render, Body, Res } from '@nestjs/common';
import { UsersService } from './users.service';
import { Response } from 'express';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get('register')
@Render('register')
showRegisterForm() {
return;
}
@Post('register')
async register(@Body() body: any, @Res() res: Response) {
// Để đơn giản, không có xác thực phức tạp ở đây
await this.usersService.create(body);
res.redirect('/auth/login'); // Chuyển hướng đến trang đăng nhập sau khi đăng ký thành công
}
}
Route Xác Thực Đăng Nhập
Tạo một auth.controller.ts mới để xử lý các yêu cầu đăng nhập.
nest generate controller auth
Chỉnh sửa src/auth/auth.controller.ts. Tại đây, chúng ta sẽ gọi phương thức validateUser. Nếu xác thực thành công, chúng ta sẽ chuyển hướng đến trang chính của blog.
Nếu thất bại, để đơn giản, chúng ta sẽ không xử lý đặc biệt cho thời điểm này. Trong một ứng dụng thực tế, bạn thường sẽ trả về một thông báo lỗi.
typescript
// src/auth/auth.controller.ts
import { Controller, Get, Post, Render, Body, Res, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { Response } from 'express';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Get('login')
@Render('login')
showLoginForm() {
return;
}
@Post('login')
async login(@Body() body: any, @Res() res: Response) {
const user = await this.authService.validateUser(body.username, body.password);
if (!user) {
throw new UnauthorizedException();
}
// Xác thực thành công
res.redirect('/posts');
}
}
Cuối cùng, nhập UsersModule và AuthModule vào app.module.ts.
typescript
// src/app.module.ts
// ... imports
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [
// ... TypeOrmModule.forRoot(...)
PostsModule,
UsersModule,
AuthModule,
],
// ...
})
export class AppModule {}
Tại thời điểm này, chúng ta đã hoàn thành một hệ thống đăng ký người dùng cơ bản và logic xác thực đăng nhập. Người dùng có thể tạo tài khoản, và ứng dụng có thể xác minh danh tính của họ.
Hãy thử nghiệm! Khởi động dự án của bạn:
npm run start:dev
Truy cập http://localhost:3000/users/register để đăng ký.
Sau khi đăng ký thành công, bạn sẽ tự động được chuyển hướng đến http://localhost:3000/auth/login để đăng nhập.
Bạn có thể thử nghiệm kết quả khi nhập đúng và sai thông tin tài khoản. Ví dụ, nếu bạn nhập sai thông tin, trang sẽ hiển thị lỗi 401 Unauthorized.
Tuy nhiên, việc đăng nhập hiện tại chỉ là một quá trình một lần, hoàn toàn để trải nghiệm quy trình xác thực đăng nhập; máy chủ không ghi nhớ trạng thái đăng nhập của người dùng.
Trong bài viết tiếp theo, chúng ta sẽ hoàn thiện logic xác thực đăng nhập để đạt được sự bền vững thật sự của phiên người dùng và hạn chế quyền truy cập vào các trang và tính năng dựa trên quyền hạn của người dùng.
Theo dõi chúng tôi trên X: @LeapcellHQ
Đọc thêm trên blog của chúng tôi
Các Bài Viết Liên Quan:
- Cách Hosting Dự Án Golang Miễn Phí (Ví Dụ với Gin)
- Cách Hosting Dự Án Rust Trên Đám Mây Miễn Phí
- Cách Chạy Puppeteer Trên Đám Mây Miễn Phí: So Sánh Các Giải Pháp
- Vercel - Giải Pháp Tốt Nhất Để Hosting Dự Án Next.js