Menu

Sửa lỗi CORS trong Express, có và không dùng package cors

Lỗi CORS nghĩa là trình duyệt đã chặn một trang đọc response từ API của bạn vì response đó không cho phép origin của trang. Hãy sửa ở phía máy chủ bằng cách gửi Access-Control-Allow-Origin và trả lời request preflight OPTIONS, tự viết hoặc dùng package cors.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Lỗi CORS ("No 'Access-Control-Allow-Origin' header is present") nghĩa là một trang web ở một origin, chẳng hạn http://localhost:5173, đã gọi API của bạn ở một origin khác, và trình duyệt từ chối cho trang đọc response vì response không nói rằng origin đó được phép. Cách sửa nằm ở máy chủ: gửi Access-Control-Allow-Origin kèm origin của trang, và trả lời request preflight OPTIONS của trình duyệt bằng các phương thức và header bạn chấp nhận. Trong Express, việc đó chỉ cần vài dòng middleware, hoặc app.use(cors()) từ package cors.

Response là danh sách sản phẩm, và tab Headers của nó cho thấy access-control-allow-origin: https://app.example.com: một trang thuộc origin đó được phép đọc nó. Giờ hãy gửi OPTIONS /api/products/1 với các header Origin: https://app.example.com và Access-Control-Request-Method: PUT. Đó là preflight mà trình duyệt gửi trước một PUT, và câu trả lời là 204 kèm các phương thức và header được phép, không có body.

Trình duyệt kiểm tra những gì

Các trang cùng origin không cần gì cả. Với một request khác origin, trình duyệt đọc các header response sau:

HeaderÝ nghĩa
Access-Control-Allow-OriginOrigin duy nhất được phép đọc response, hoặc * cho mọi origin
Access-Control-Allow-MethodsCác phương thức được phép trong request thật (chỉ ở preflight)
Access-Control-Allow-HeadersCác header request mà trang được gửi, như Authorization (chỉ ở preflight)
Access-Control-Allow-Credentialstrue nếu trang được gửi cookie và đọc câu trả lời
Access-Control-Max-AgeSố giây trình duyệt được lưu cache câu trả lời preflight
Vary: OriginBáo cho bộ đệm biết câu trả lời phụ thuộc vào header request Origin

Một request GET đơn giản hoặc POST kiểu biểu mẫu được gửi thẳng tới máy chủ, và trình duyệt kiểm tra Access-Control-Allow-Origin trên câu trả lời. Máy chủ vẫn xử lý request; CORS chỉ quyết định trang có được thấy kết quả hay không. Một request có body JSON, có header Authorization, hoặc dùng phương thức như PUT hay DELETE sẽ được đi trước bởi một request preflight OPTIONS, và request thật chỉ được gửi nếu câu trả lời preflight cho phép. Đó là lý do một middleware CORS phải trả lời OPTIONS trước khi bất kỳ route hay bước kiểm tra xác thực nào chạy.

Cho phép nhiều origin, kèm credentials

Access-Control-Allow-Origin chứa một origin hoặc *, không bao giờ là một danh sách. Để cho phép nhiều origin, hãy so sánh header Origin của request với một danh sách cho phép và gửi lại đúng origin đó khi khớp:

Request đầu tiên không có header Origin, giống một lời gọi từ curl hoặc từ trang cùng origin, nên nó nhận {"origin":null} và không có header CORS nào. Gửi GET /api/me với header Origin: http://localhost:5173 thì tab Headers cho thấy origin đó được gửi lại, kèm access-control-allow-credentials: true. Đổi header thành Origin: https://evil.example thì cả hai header CORS biến mất: máy chủ vẫn trả lời, nhưng trình duyệt sẽ chặn trang đọc câu trả lời. Access-Control-Allow-Credentials: true cho phép trang gửi cookie (với fetch(url, { credentials: 'include' })), và trình duyệt từ chối nó khi đi cùng *, nên credentials luôn cần đúng origin cụ thể.

Package cors

Package cors làm cùng công việc đó thông qua các tùy chọn. Cài đặt bằng npm install cors:

const cors = require('cors');

// Any origin, no cookies: fine for a public read-only API.
app.use(cors());

// Only your frontend, with cookies.
app.use(cors({
    origin: ['https://app.example.com', 'http://localhost:5173'],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

// One route only.
app.get('/api/public', cors(), handler);

Khi gọi không có tùy chọn, cors() gửi Access-Control-Allow-Origin: * và trả lời mọi preflight. Chỉ dùng * cho dữ liệu công khai không cần đăng nhập.

Những lỗi thường gặp

Thêm CORS sau các route hoặc sau middleware xác thực. Request preflight OPTIONS không mang header Authorization, nên một bước kiểm tra xác thực đặt trước sẽ trả lời nó bằng 401 và trình duyệt báo lỗi CORS. Hãy đăng ký middleware CORS đầu tiên.

Đặt * kèm credentials. Trình duyệt sẽ từ chối response. Hãy gửi lại đúng origin cụ thể.

Cố sửa CORS ở frontend. Các tùy chọn request như mode: 'no-cors' không gỡ bỏ được giới hạn; chúng cho bạn một response "mờ" (opaque) mà bạn không đọc được. Chỉ header từ máy chủ mới sửa được lỗi này.

Để biết app.use() và thứ tự middleware hoạt động thế nào, xem middleware trong Express.

Câu hỏi thường gặp

Vì sao API chạy được trong Postman hoặc curl nhưng không chạy trên trình duyệt?

CORS chỉ được trình duyệt áp dụng. Postman, curl và các máy chủ khác bỏ qua header CORS, nên chúng đọc được response bất kể thế nào. Trình duyệt từ chối đưa response cho script của trang thuộc origin khác, trừ khi máy chủ cho phép origin đó.

CORS có phải tính năng bảo mật cho máy chủ của tôi không?

Nó bảo vệ người dùng, không bảo vệ máy chủ của bạn. Nó ngăn một trang độc hại đọc response từ API của bạn bằng cookie của người dùng. Bất kỳ ai vẫn có thể gọi API của bạn từ một script hoặc một máy chủ, nên xác thực và phân quyền vẫn là việc của bạn.

Thế nào được coi là một origin khác?

Một origin gồm scheme, host và port gộp lại. http://localhost:5173 và http://localhost:3000 là hai origin khác nhau vì port khác nhau, còn https://example.com và https://api.example.com khác nhau ở host.

Có thể tránh CORS khi phát triển không?

Có, bằng cách biến request thành cùng origin: cấu hình dev server của frontend (ví dụ server.proxy của Vite) để chuyển tiếp /api tới máy chủ Express. Khi đó trình duyệt chỉ nói chuyện với một origin và không cần header CORS nào.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu