Authentication và authorization: endpoint mà ai cũng gọi được
Hai từ này nghe giống nhau, và sự khác biệt giữa chúng chính là nơi những vụ vi phạm bảo mật thật sự xảy ra.
- Authentication (xác thực) là bạn là ai? — đăng nhập, chứng minh danh tính.
- Authorization (phân quyền) là bạn được phép làm gì? — liệu người dùng đã đăng nhập này có được phép thực hiện hành động này trên dữ liệu này hay không.
AI khá ổn ở khoản xác thực; các thư viện đã xử lý phần lớn việc đó. Phân quyền là chỗ nó thường xuyên thất bại, vì phân quyền phụ thuộc vào các quy tắc riêng của ứng dụng bạn, và AI không biết những quy tắc đó. Thảm họa kinh điển trong sách giáo khoa:
// VULNERABLE: checks you're logged in, but not WHO you are
app.get("/admin/export-all-users", requireLogin, (req, res) => {
res.json(db.getAllUsers()); // any logged-in user can hit this
});
đó được "bảo vệ" — bạn phải đăng nhập. Nhưng bất kỳ người dùng đã đăng nhập nào, kể cả người vừa đăng ký ba mươi giây trước, cũng có thể gọi nó và tải về dữ liệu của mọi người dùng. Có xác thực, không có phân quyền. Cách sửa là kiểm tra quyền ngay trên hành động đó:
// SAFE: confirms this user is actually an admin
app.get("/admin/export-all-users", requireLogin, (req, res) => {
if (!req.user.isAdmin) return res.status(403).send("Forbidden");
res.json(db.getAllUsers());
});
Hãy hình dung hai cánh cổng mà một yêu cầu (request) phải đi qua. Authentication hỏi "bạn đã đăng nhập chưa?"; authorization hỏi "bạn có được phép làm việc này không?" Vụ vi phạm xảy ra khi một ứng dụng xây cổng thứ nhất mà quên mất cổng thứ hai:
request ──▶ ┌──────────────────┐ ──▶ ┌──────────────────┐ ──▶ action
│ AUTHENTICATION │ │ AUTHORIZATION │ runs
│ "logged in?" │ │ "may THIS user │
│ │ │ do THIS action?" │
└──────────────────┘ └──────────────────┘
✓ most apps ✗ often missing
build this → the breach
EXAMPLE — /admin/export-all-users
logged-in user ──▶ [auth ✓] ──▶ [ no authz check ] ──▶ ALL users dumped
logged-in user ──▶ [auth ✓] ──▶ [ isAdmin? → 403 ] ──▶ blocked ✓
Cái bẫy y hệt xuất hiện ở quy mô nhỏ hơn khắp nơi: một endpoint trả về đơn hàng số #1234 mà không kiểm tra xem đơn hàng đó có thuộc về người dùng đang yêu cầu hay không. Bất kỳ ai cũng có thể đổi số đó trong và đọc đơn hàng của người khác. Quy tắc thì không hào nhoáng nhưng tuyệt đối: kiểm tra phân quyền trên mọi endpoint chạm vào dữ liệu, và đừng tin tưởng một ID đến từ phía client. Đừng cho rằng một URL bị giấu là an toàn chỉ vì nó bị giấu — "không ai biết cái này tồn tại" không phải là một biện pháp bảo mật.