Phân biệt AuthN và AuthZ
Authentication: xác minh danh tính (nhập đúng email + mật khẩu). Authorization: kiểm tra quyền (user có được xoá khoá học không?). Luôn kiểm tra cả hai ở mọi endpoint.
// File: authz.js
// VÍ DỤ LỖI: chỉ kiểm tra "đã đăng nhập", không kiểm tra quyền
app.delete("/api/courses/:id", requireAuth, async (req, res) => {
await db.course.delete({ where: { id: Number(req.params.id) } });
res.status(204).end();
});
// ĐÚNG: phải có quyền admin mới được xoá
app.delete("/api/courses/:id", requireAuth, requireAdmin, async (req, res) => {
...
});
JWT: token tự chứa thông tin
JWT gồm 3 phần (header.payload.signature) được ký bằng secret. Server không cần lưu state — giải mã và tin vào chữ ký là biết ai đang gọi. Nhưng vì không thể thu hồi tức thì, đừng nhét quá nhiều thứ vào token.
// File: jwt.js
import jwt from 'jsonwebtoken';
// Đăng nhập thành công -> phát token
const token = jwt.sign({ userId: user.id, role: user.role },
process.env.JWT_SECRET, { expiresIn: "1d" });
// Middleware xác thực: đọc token, giải mã, gắn vào req
function requireAuth(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "Thiếu token" });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: "Token không hợp lệ hoặc hết hạn" });
}
}
Ký bằng secret mạnh nằm trong biến môi trường (không hardcode). Đặt expiresIn ngắn (giờ–1 ngày). Không bao giờ lưu mật khẩu hay thông tin nhạy vào token. Frontend nên lưu token an toàn, không để dính XSS.
Session: server giữ trạng thái
Session khác JWT ở chỗ server lưu session id trong store (thường là Redis). Server có thể thu hồi session bất kỳ lúc nào — hữu ích khi cần “đăng xuất khắp nơi” ngay lập tức.
Hash mật khẩu: bcrypt, không phải MD5
Không bao giờ lưu mật khẩu thô. Dùng bcrypt — thuật toán hash có salt, mỗi lần ra chuỗi khác nhau, nên so sánh bằng hàm chuyên dụng.
// File: bcrypt.js
import bcrypt from 'bcrypt';
// Đăng ký: hash trước khi lưu
const hash = await bcrypt.hash(password, 12); // 12 vòng muối
await db.user.create({ data: { email, password: hash } });
// Đăng nhập: so sánh hash, không so chuỗi
const ok = await bcrypt.compare(inputPassword, user.password);
if (!ok) return res.status(401).json({ error: "Sai mật khẩu" });
OAuth2: để bên thứ ba đăng nhập hộ
Khi người dùng “Đăng nhập bằng Google”, bạn đang dùng OAuth2: hệ thống bên ngoài xác thực rồi cấp token cho app của bạn. Không cần tự quản lý mật khẩu.
| Cách | Lưu trữ | Thu hồi | Dùng khi |
|---|---|---|---|
| Session | Server (Redis/DB) | Tức thì | App web cần đăng xuất chủ động |
| JWT | Client tự giữ | Chờ hết hạn | API cho nhiều client, microservices |
| OAuth2 | Bên thứ ba | Theo nhà cung cấp | Đăng nhập bằng Google/Facebook |
❓ Sự khác biệt cốt lõi giữa authentication và authorization?
- Phân biệt rõ AuthN và AuthZ
- Triển khai JWT có expires và secret trong env
- Hash mật khẩu bằng bcrypt
- Không hardcode secret hay nhét mật khẩu vào token