Bảo mật ứng dụng bằng cách xác thực dữ liệu ở cấp truy vấn bằng thư viện xác thực Joi rất đơn giản. Dưới đây là hướng dẫn chi tiết cách xác thực schema trong Node.js bằng Joi.
Chấp nhận dữ liệu chưa được kiểm tra và xác thực vào một ứng dụng web có thể tạo ra lổ hổng bảo mật, và sự cố khó lường dễ phát sinh từ dữ liệu không hợp lệ.
Node.js ORM, chẳng hạn như Sequelize và TypeORM, cho phép bạn đặt các quy tắc xác thực ngay lập tức ở cấp độ ứng dụng. Trong suốt quá trình phát triển API, dữ liệu đi từ các truy vấn HTTP tới những endpoint cụ thể. Điều này xảy ra ở cấp độ truy vấn, vì thế, xác thực mặc định do các ORM cung cấp không dược áp dụng cho chúng.
Joi là một mô tả schema và xác thực dữ liệu cho JavaScript. Tại đây, bạn sẽ học cách dùng thư viện xác thực Joi để xác thực dữ liệu ở cấp độ truy vấn.
Thiết lập dự án thử nghiệm
Để minh họa cách Joi xác thực dữ liệu, bạn sẽ xây dựng một ứng dụng demo đơn giản mô phỏng một app thực tế.
Đầu tiên, tạo một thư mục dự án và truy cập nó bằng cách chạy lệnh sau:
mkdir demoapp && cd demoapp
Tiếp theo, khởi tạo npm trong thư mục dự án bằng cách chạy:
npm init -y
Tiếp theo, bạn cần cài đặt một số phần phụ thuộc. Ở hướng dẫn này, bạn cần:
- Express: Express là một framework Node.js, cung cấp một nhóm tính năng mạnh mẽ cho web và ứng dụng mobile. Express khiến nó dễ xây dựng các ứng dụng backend hơn với Node.js.
- Joi: Joi là một thư viện xác thực dữ liệu cho Node.js.
Cài đặt các phần phụ thuộc với trình quản lý gói node bằng cách chạy lệnh sau:
npm install express joi
Tiếp theo, tạo file index.js trong thư mục gốc và thêm khối code sau vào nó:
const express = require("express");
const router = require("./routes");
const port = 3000;
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(router);
app.listen(port, () => {
console.log("app listening on port 3000!");
});
Khối code trên thiết lập một server Express đơn giản. Nó cấu hình phần mềm trung gian để phân tích dữ liệu truy vấn đến, xử lý truy vấn đến và khởi động server nghe theo các truy vấn đến trên cổng 3000.
Định tuyến và xử lý yêu cầu
Phương thức đơn giản nhất là bạn sẽ tạo một phần mềm trung gian xử lý truy vấn, trả về một mã trạng thái, cùng với nội dung truy vấn, như một phản hồi cho mọi yêu cầu cố gắng gửi dữ liệu đến ứng dụng của bạn.
Tạo file handler.js trong thư mục gốc của dự án và thêm khối code bên dưới:
const demoHandler = (req, res, next) => {
res.send({
code: 201,
data: req.body,
});
next();
};
module.exports = demoHandler;
Tiếp theo, tạo file router.js trong thư mục gốc của dự án và thêm khối code bên dưới vào file của bạn:
const express = require("express");
const demoHandler = require("./handler");
const router = express.Router();
router.post("/signup", demoHandler);
module.exports = router;
Tạo Joi Schema
Một Joi schema đại diện cho một cấu trúc được mong đợi của một đối tượng dữ liệu cụ thể và các quy tắc xác thực.
Để tạo một Joi schema, bạn có thể dùng phương thức Joi.object() và xâu chuỗi các quy tắc xác thực khác nhau do Joi đưa ra để xác định cấu trúc & các yêu cầu xác thực cho dữ liệu của bạn.
Ví dụ:
const exampleSchema = Joi.object({
name: Joi.string().min(3).required(),
});
Ví dụ trên mô tả một Joi schema đơn giản với thuộc tính name. Thuộc tính name có giá trị của Joi.string().min(3).required(). Điều này có nghĩa là giá trị name sẽ là một chuỗi, với độ dài tối thiểu bắt buộc phải có là 3 ký tự.
Dùng Joi, bạn có thể xâu chuỗi các phương thức khác nhau để thêm nhiều giới hạn xác thực hơn cho từng trường được xác định trong schema.
Ví dụ:
const userSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
age: Joi.number().min(18).optional(),
employed: Joi.boolean().optional(),
phone: Joi.string()
.regex(/^\\d{3}-\\d{3}-\\d{4}$/)//"123-456-7890"
.required(),
address: Joi.object({
street: Joi.string().min(3).required(),
city: Joi.string().min(3).required(),
state: Joi.string().min(3).required(),
zip: Joi.number().min(3).required(),
}).required(),
hobbies: Joi.array().items(Joi.string()).required(),
}).options({ abortEarly: false });
userSchema xác định các yêu cầu ràng buộc sau cho từng thuộc tính:
- Email: Phải là một chuỗi email hợp lệ.
- Password: Phải là một chuỗi với một số gồm 6 ký tự.
- Age: Một số tùy chọn với giá trị tối thiểu là 18.
- Employed: Một boolean tùy chọn.
- Phone: Một chuỗi được yêu cầu phù hợp với biểu thức chính quy cụ thể (/^\d{3}-\d{3}-\d{4}$/).
- Address: Một đối tượng đại diện cho địa chỉ của người dùng với những thuộc tính phụ sau:
- Street: Một chuỗi bắt buộc có độ dài tối thiểu 3 ký tự.
- City: Một chuỗi bắt buộc có độ dài tối thiểu 3 ký tự.
- State: Một chuỗi bắt buộc có độ dài tối thiểu 3 ký tự.
- Zip: Một chuỗi bắt buộc có giá trị tối thiểu bằng 3.
- Hobbies: Mảng bắt buộc của các chuỗi.
Ngoài các giới hạn, userSchema đặt tùy chọn abortEarly sang false. Mặc định, Joi dừng chạy chương trình ngay khi nó gặp lỗi đầu tiên và in lỗi cho console. Tuy nhiên, đặt tùy chọn này sang false đảm bảo Joi kiểm tra toàn bộ schema và in tất cả lỗi gặp phải cho console.
Xác thực dữ liệu với Joi
Tạo file validation.js và thêm code userSchema cho nó.
Ví dụ:
//validation.js
const Joi = require("joi");
const userSchema = Joi.object({
//...
}).options({ abortEarly: false });
module.exports = userSchema;
Sau đó, tạo middleware chặn payload truy vấn và xác minh chúng dựa trên schema được cung cấp bằng cách thêm code sau vào bên dưới code userSchema.
const validationMiddleware = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
// Xử lý lỗi xác thực
console.log(error.message);
res.status(400).json({ errors: error.details });
} else {
// Dữ liệu hợp lệ, tiếp tục xử lý middleware tiếp theo
next();
}
};
};
Khi một yêu cầu được thực hiện, middleware sẽ gọi phương thức validate của schema để xác thực nội dung truy vấn. Nếu có bất kỳ lỗi xác thực xảy ra, middleware gửi phản hồi 400 Bad Request kèm thông báo lỗi được truy xuất từ chi tiết lỗi xác thực.
Mặt khác, nếu xác thực thành công mà không có lỗi, middeware gọi hàm next().
Cuối cùng, xuất validationMiddleware và userSchema.
module.exports = {
userSchema,
validationMiddleware,
};
Kiểm tra các điều kiện xác thực
Nhập validationMiddleware và userSchema vào file router.js, đồng thời thiết lập middleware như sau:
const { validationMiddleware, userSchema } = require("./validation");
router.post("/signup", validationMiddleware(userSchema), demoHandler);
Khởi động ứng dụng bằng cách chạy lệnh bên dưới:
node index.js
Sau đó, tạo truy vấn HTTP POST tới localhost:3000/signup bằng dữ liệu kiểm tra bên dưới. Bạn có thể đạt được điều này bằng cách dùng cURL hoặc bất kỳ client API khác.
{
"email": "user@example", // Invalid email format
"password": "pass", // Password length less than 6 characters
"age": 15, // Age below 18
"employed": true,
"hobbies": ["reading", "running"],
"phone": "123-456-789", // Invalid phone number format
"address": {
"street": "123",
"city": "Example City",
"state": "Example State",
"zip": 12345
}
}
Truy vấn này sẽ thất bại và trả về một đối tượng lỗi khi payload chứa rất nhiều trường không hợp lệ, chẳng hạn như email, mật khẩu, độ tuổi và số điện thoại. Dùng đối tượng lỗi được cung cấp, bạn có thể xử lý lỗi phù hợp.
Đơn giản hóa xác thực dữ liệu bằng Joi
Trên đây là những điều bạn cần biết về xác thực dữ liệu bằng Joi. Như bạn thấy cũng khá đơn giản phải không? Chúc các bạn thành công!