Hướng dẫn

Cách sửa lỗi CORS (và vì sao xảy ra)

Lỗi CORS nghĩa là trang web của bạn đã cố đọc phản hồi từ một origin khác (một miền, cổng hoặc giao thức khác), và máy chủ đó không gửi các header cho phép điều này. Trình duyệt chặn phản hồi để bảo vệ người dùng. Bạn sửa nó trên máy chủ, bằng cách trả về header Access-Control-Allow-Origin cho origin của site bạn và xử lý các yêu cầu preflight OPTIONS; hoặc bạn tránh nó bằng cách gửi yêu cầu qua back end của chính bạn trên cùng origin.

Đây là một trong những lỗi gây bối rối nhất trong phát triển web vì yêu cầu thường vẫn đến được máy chủ. Hướng dẫn này giải thích điều gì đang xảy ra và cách sửa từng biến thể.

"Origin" nghĩa là gì

Một origin là sự kết hợp của giao thức, tên miền và cổng:

URLOrigin
https://myapp.com/dashboardhttps://myapp.com
https://api.myapp.com/usershttps://api.myapp.com (khác subdomain, nên khác origin)
http://localhost:5173http://localhost:5173
http://localhost:3000http://localhost:3000 (khác cổng, nên khác origin)

Trình duyệt tuân theo same-origin policy: JavaScript trên trang có thể tự do đọc phản hồi từ chính origin của nó, nhưng không đọc được từ origin khác. CORS (Cross-Origin Resource Sharing) là hệ thống cho phép máy chủ chủ động đồng ý: "các phản hồi từ tôi có thể được đọc bởi các trang từ những origin này."

Hai hệ quả khiến nhiều người bất ngờ:

  • Yêu cầu thường vẫn đến được máy chủ. CORS thường chặn code của bạn đọc phản hồi, chứ không chặn máy chủ nhận yêu cầu. Một POST có thể đã được lưu dù bạn thấy lỗi.
  • Chỉ trình duyệt mới áp dụng nó. curl, Postman và code phía máy chủ đều bỏ qua CORS hoàn toàn. Đó là lý do "chạy được trong Postman" không cho bạn biết gì về CORS.

Bước 1: Đọc đúng thông báo lỗi

Mở công cụ phát triển (chuột phải → Inspect), vào Console, và đọc toàn bộ thông báo. Cách diễn đạt của Chrome cho bạn biết bạn đang gặp vấn đề nào:

Thông báo chứaĐiều gì sai
No 'Access-Control-Allow-Origin' header is presentMáy chủ không gửi header nào cả, hoặc yêu cầu thất bại trước khi đến mã CORS của bạn (ví dụ 404 hoặc 500)
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied originMáy chủ cho phép một origin khác với origin của bạn
Response to preflight request doesn't pass access control checkYêu cầu preflight OPTIONS thất bại
...must not be the wildcard '*' when the request's credentials mode is 'include'Bạn đang gửi cookie, nhưng máy chủ trả về *
Request header field authorization is not allowedMáy chủ không liệt kê header đó trong Access-Control-Allow-Headers

Sau đó mở tab Network, tìm yêu cầu lỗi (và mọi yêu cầu OPTIONS đi trước nó), và xem mã trạng thái cùng các header phản hồi.

Hãy kiểm tra mã trạng thái trước. Nhiều "lỗi CORS" thực ra là một lỗi máy chủ được ngụy trang. Nếu máy chủ crash hoặc trả về 404, phản hồi lỗi của nó thường thiếu header CORS, nên trình duyệt báo lỗi CORS thay vì vấn đề thật sự. Sửa lỗi 500 hoặc 404, và lỗi CORS có thể biến mất.

Bước 2: Quyết định liệu trình duyệt có nên gọi API đó trực tiếp không

Trước khi thêm header, hãy tự hỏi: trình duyệt có nên gọi API này trực tiếp không?

  • API bên thứ ba cần khóa bí mật (OpenAI, khóa bí mật của Stripe, hầu hết dịch vụ email) không bao giờ nên được gọi từ trình duyệt. Khóa sẽ hiển thị với mọi người truy cập. Hãy gọi từ back end, và vấn đề CORS biến mất, vì các yêu cầu server-to-server không chịu sự ràng buộc của CORS. Xem cách giữ khóa API an toàn.
  • API bên thứ ba mà bạn không kiểm soát và không gửi header CORS thì không thể sửa từ phía bạn. Hãy gọi nó từ back end.
  • API của chính bạn có thể được sửa bằng cách cấu hình đúng. Tiếp tục bên dưới.

Bước 3: Sửa trên máy chủ của bạn

Máy chủ phải thêm các header đúng vào mọi phản hồi, kể cả phản hồi lỗi.

Với yêu cầu thật, gửi:

  • Access-Control-Allow-Origin: https://myapp.com (origin chính xác của frontend, không có dấu gạch chéo cuối)
  • Vary: Origin, nếu giá trị phụ thuộc vào origin nào đã yêu cầu

Với yêu cầu preflight OPTIONS, trả về trạng thái thành công (200 hoặc 204) cùng:

  • Access-Control-Allow-Origin: https://myapp.com
  • Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
  • Access-Control-Allow-Headers: Content-Type, Authorization (mọi header tùy chỉnh bạn gửi)
  • Tùy chọn Access-Control-Max-Age: 600, để trình duyệt cache preflight trong một khoảng thời gian

Hầu hết framework có một package hoặc cài đặt làm việc này. Trong Express là package cors; trong FastAPI là CORSMiddleware; trong ASP.NET Core là AddCors; trong Spring là @CrossOrigin hoặc cấu hình CORS. Một cấu hình Express điển hình:

import cors from "cors";

app.use(cors({
  origin: ["https://myapp.com", "http://localhost:5173"],
  credentials: true,
}));

Đặt xử lý CORS trước các route và xác thực, để các yêu cầu preflight (vốn không mang thông tin đăng nhập) không bị từ chối với 401.

Với công cụ tạo app bằng AI, hãy mô tả cấu hình thật chính xác:

Frontend tại https://myapp.com gọi API tại https://api.myapp.com và nhận "Response to preflight request doesn't pass access control check". Cấu hình CORS trên API để chỉ cho phép đúng https://myapp.com và origin của bản xem trước, cho phép GET, POST, PATCH, DELETE với header Content-Type và Authorization, xử lý OPTIONS trước phần xác thực, và cũng đưa header CORS vào các phản hồi lỗi.

Bước 4: Xử lý cookie và credentials

Nếu frontend của bạn gửi cookie (ví dụ phiên đăng nhập) đến một origin khác, cả hai phía phải thống nhất:

  • Frontend đặt credentials: "include" trên fetch, hoặc withCredentials: true trong axios.
  • Máy chủ gửi Access-Control-Allow-Credentials: true.
  • Access-Control-Allow-Origin của máy chủ phải là origin chính xác, không phải *.

Cookie liên trang cũng cần SameSite=None; Secure, và một số trình duyệt hạn chế cookie bên thứ ba nghiêm ngặt hơn nữa. Đó là thêm một lý do để ưu tiên cách tiếp cận ở Bước 5.

Bước 5: Tránh CORS bằng thiết lập cùng origin

Thường thì cách sửa gọn nhất là không thực hiện yêu cầu xuyên origin nào cả. Hãy phục vụ API từ cùng origin với trang, ví dụ tại https://myapp.com/api/...:

  • Khi phát triển, hầu hết dev server có thể làm proxy. Tùy chọn server.proxy của Vite có thể chuyển tiếp /api đến back end của bạn trên một cổng khác, nên trình duyệt chỉ nói chuyện với một origin.
  • Trên production, định tuyến /api đến back end thông qua cài đặt rewrite hoặc proxy của host, hoặc phục vụ frontend và API từ cùng một máy chủ.

Khi đó frontend gọi một đường dẫn tương đối như fetch("/api/orders"). Không có yêu cầu xuyên origin, không có preflight xuyên origin, và không có rắc rối về cookie. Điều này cũng có nghĩa là bạn không phải mã cứng một tên miền API khác nhau giữa bản xem trước và production.

Đổi frontend để gọi back end bằng đường dẫn /api tương đối thay vì URL đầy đủ, và thiết lập một dev proxy để /api chuyển tiếp đến máy chủ API. Sau đó xóa mọi cài đặt CORS không còn cần thiết.

Những việc không nên làm

  • mode: "no-cors" trong fetch. Nó không vượt qua được gì; nó chỉ cho code của bạn một phản hồi rỗng, không đọc được.
  • Tiện ích trình duyệt tắt CORS. Chúng chỉ thay đổi trình duyệt của bạn. Mọi người truy cập vẫn thấy lỗi.
  • Dịch vụ "CORS proxy" công khai cho app thật. Chúng có thể đọc và thay đổi mọi thứ đi qua chúng, và chúng thường xuyên ngừng hoạt động.
  • Access-Control-Allow-Origin: * trên API có đăng nhập. Nó vốn không hoạt động với credentials, và là mặc định sai cho dữ liệu riêng tư.
  • Phản chiếu mọi header Origin trả về khi cho phép credentials. Điều đó cho phép bất kỳ website nào thực hiện yêu cầu có đăng nhập thay cho người dùng của bạn. Hãy dùng danh sách cho phép.

Nguyên nhân thường gặp trong app do AI xây dựng

Tình huốngCách sửa
Frontend gọi API bên thứ ba bằng khóa bí mậtChuyển lời gọi sang back end
Chạy được trong bản xem trước, lỗi trên tên miền đã publishThêm origin của bản đã publish và tên miền riêng vào danh sách cho phép, hoặc dùng đường dẫn /api tương đối
Lỗi chỉ xảy ra với POST, PUT hoặc DELETEPreflight chưa được xử lý; thêm xử lý OPTIONS và các header được phép
Lỗi xuất hiện sau khi thêm đăng nhậpCredentials cần đúng origin và Allow-Credentials
Báo "lỗi CORS" nhưng tab Network hiện 500Sửa lỗi máy chủ trước
URL API được mã cứng thành localhostDùng đường dẫn tương đối hoặc một cài đặt theo từng môi trường

CORS trong Mythex

Khi một dự án Mythex có cả website và máy chủ API, cả hai chạy như các service trong cùng dự án, và bạn có thể chuyển đổi giữa chúng trong bản xem trước. Yêu cầu agent để frontend gọi back end qua đường dẫn /api tương đối, được proxy khi phát triển, sẽ tránh được hầu hết vấn đề CORS. Nếu bạn thật sự cần gọi xuyên origin, hãy đưa cho agent đúng lỗi từ console và các origin liên quan. Kiểm tra kết quả trên app đã publish, vì địa chỉ của nó khác với bản xem trước. Xem Ứng dụng nhiều service và API backend trong tài liệu, cùng các hướng dẫn về gỡ lỗi app do AI xây dựng và frontend so với backend.

Danh sách kiểm tra CORS

  • Đã đọc toàn bộ lỗi trong console và mã trạng thái của yêu cầu lỗi
  • Đã loại trừ khả năng 404 hoặc 500 bị che khuất
  • API dùng khóa bí mật được gọi từ back end, không phải trình duyệt
  • Máy chủ gửi Access-Control-Allow-Origin với danh sách origin chính xác
  • Preflight OPTIONS được trả lời với các method và header được phép, trước phần xác thực
  • Header CORS có mặt trong cả phản hồi lỗi
  • Credentials: đúng origin, Allow-Credentials: true, và cài đặt cookie đúng
  • Đã bao phủ origin của bản xem trước, bản đã publish và tên miền riêng, hoặc dùng đường dẫn /api tương đối
  • Không dùng no-cors, proxy công khai, hay phản chiếu mọi origin

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

Lỗi CORS nghĩa là gì?

Trang của bạn, được tải từ một origin, đã cố đọc phản hồi từ một origin khác, và máy chủ đó không gửi header cho phép điều này. Trình duyệt chặn code của bạn đọc phản hồi. Cách sửa nằm ở máy chủ, hoặc ở cách định tuyến yêu cầu, không nằm trong code frontend.

Tôi có thể sửa lỗi CORS từ frontend không?

Không thể sửa đúng cách. Các cài đặt như mode: 'no-cors' chỉ giấu phản hồi khỏi code của bạn. Bạn sửa bằng cách để máy chủ gửi đúng header Access-Control-Allow-Origin, hoặc gọi API từ back end của bạn hoặc một proxy cùng origin.

Access-Control-Allow-Origin: * có an toàn không?

Điều đó ổn với dữ liệu công khai mà ai cũng có thể đọc, như một API công khai không cần đăng nhập. Nó không dùng được với cookie hay credential khác, và với API riêng tư bạn nên liệt kê chính xác các origin được phép.

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

CORS chỉ được trình duyệt áp dụng. Các công cụ như Postman và curl không áp dụng nó, nên một yêu cầu có thể thành công ở đó nhưng vẫn bị chặn khi JavaScript của trang thực hiện.

Preflight request là gì?

Với các yêu cầu không đơn giản, như POST JSON hoặc yêu cầu có header Authorization, trình duyệt trước tiên gửi một yêu cầu OPTIONS để hỏi máy chủ những method và header nào được phép. Nếu yêu cầu OPTIONS đó thất bại hoặc thiếu header đúng, yêu cầu thật sự sẽ không bao giờ được gửi đi.

Mythex · Pricing · Xây dựng mọi thứ với AI · Mẫu website và ứng dụng · So sánh Mythex · Thuật ngữ xây dựng ứng dụng bằng AI · Công cụ miễn phí để xây dựng ứng dụng bằng AI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو 简体中文 繁體中文(香港) 繁體中文(台灣)