DDevArchive
Đăng nhập

Authentication & Authorization: JWT, Session, OAuth2

Authentication trả lời "bạn là ai", Authorization trả lời "bạn được làm gì". Hai khái niệm này hay bị nhầm, và việc nhầm chúng là nguồn gốc của lỗ hổng bảo mật nghiêm trọng.

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" });
  }
}
Quy tắc an toàn JWT

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áchLưu trữThu hồiDùng khi
SessionServer (Redis/DB)Tức thìApp web cần đăng xuất chủ động
JWTClient tự giữChờ hết hạnAPI cho nhiều client, microservices
OAuth2Bên thứ baTheo 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