指南

如何修正 CORS 錯誤(以及為什麼會發生)

CORS 錯誤代表你的網頁嘗試讀取來自不同來源(另一個網域、連接埠或通訊協定)的回應,而該伺服器沒有傳送允許存取的標頭。為了保護使用者,瀏覽器會阻擋這個回應。修正方法是在伺服器端進行處理:為你網站的來源回傳 Access-Control-Allow-Origin 標頭,並處理 OPTIONS 預檢請求;或者,透過你自己在同一來源下的後端發送請求,從而避開這個問題。

這是網頁開發中最令人困惑的錯誤之一,因為請求往往能順利到達伺服器。本指南會說明發生了什麼事,以及如何修正每一種變體。

「來源」是什麼意思

來源是通訊協定、網域與連接埠的組合:

網址來源
https://myapp.com/dashboardhttps://myapp.com
https://api.myapp.com/usershttps://api.myapp.com(子網域不同,因此來源不同)
http://localhost:5173http://localhost:5173
http://localhost:3000http://localhost:3000(連接埠不同,因此來源不同)

瀏覽器遵循同源政策:頁面上的 JavaScript 可以自由讀取來自自身來源的回應,但無法讀取來自其他來源的回應。CORS(跨來源資源共享)則是讓伺服器選擇開放的機制:「來自這些來源的頁面可以讀取我的回應。」

有兩個結果會讓人感到意外:

  • 請求通常已經到達伺服器。 CORS 通常阻擋的是你的程式碼讀取回應,而不是伺服器接收請求。即使你看到錯誤,POST 可能已經儲存成功。
  • 只有瀏覽器會強制執行。 curl、Postman 以及伺服器端程式碼完全不理會 CORS。因此「在 Postman 中能運作」並不能說明任何有關 CORS 的問題。

步驟一:閱讀確切的錯誤

開啟開發者工具(右鍵 → 檢查),切換到 Console,並閱讀完整的訊息。Chrome 的訊息用語會告訴你是哪一種問題:

訊息包含問題所在
No 'Access-Control-Allow-Origin' header is present伺服器完全沒有送出該標頭,或請求在到達你的 CORS 程式碼之前就已失敗(例如 404 或 500)
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin伺服器允許的來源與你的不同
Response to preflight request doesn't pass access control checkOPTIONS 預檢請求失敗
...must not be the wildcard '*' when the request's credentials mode is 'include'你正在送出 Cookie,而伺服器卻回傳了 *
Request header field authorization is not allowed伺服器沒有在 Access-Control-Allow-Headers 中列出該標頭

接著開啟 Network 分頁,找出失敗的請求(以及之前的任何 OPTIONS 請求),並查看它的狀態碼與回應標頭。

先檢查狀態碼。 許多「CORS 錯誤」其實是偽裝成 CORS 的伺服器錯誤。如果伺服器當機或回傳 404,它的錯誤回應通常不含 CORS 標頭,因此瀏覽器報告的是 CORS,而不是真正的問題。修正 500 或 404,CORS 錯誤可能就會消失。

步驟二:先決定是否應該從瀏覽器呼叫這個 API

在加入標頭之前,先問自己:瀏覽器真的應該直接呼叫這個 API 嗎?

  • 需要密鑰的第三方 API(OpenAI、Stripe 的密鑰、大多數郵件服務)絕對不應從瀏覽器呼叫。密鑰會被每位訪客看見。請改由你的後端呼叫,CORS 問題就會消失,因為伺服器對伺服器的請求不受 CORS 限制。請參考如何保護 API 金鑰安全。
  • 你無法控制、且不送出 CORS 標頭的第三方 API,無法從你這一端修正。請由你的後端呼叫。
  • 你自己的 API 可以透過正確設定來修正。請繼續往下看。

步驟三:在伺服器端修正

伺服器必須在它的回應中加入正確的標頭,包括錯誤回應。

對於實際的請求,送出:

  • Access-Control-Allow-Origin: https://myapp.com(你前端的確切來源,結尾不要有斜線)
  • 如果該值取決於是哪個來源發出請求,則加上 Vary: Origin

對於預檢的 OPTIONS 請求,以成功狀態碼(200 或 204)回應,並包含:

  • Access-Control-Allow-Origin: https://myapp.com
  • Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
  • Access-Control-Allow-Headers: Content-Type, Authorization(你送出的每一個自訂標頭)
  • 選擇性地加上 Access-Control-Max-Age: 600,讓瀏覽器將預檢結果快取一段時間

大多數框架都有套件或設定可以完成這些事。在 Express 中使用 cors 套件;在 FastAPI 中使用 CORSMiddleware;在 ASP.NET Core 中使用 AddCors;在 Spring 中使用 @CrossOrigin 或 CORS 設定。一個典型的 Express 設定:

import cors from "cors";

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

請把 CORS 處理放在路由與驗證之前,這樣預檢請求(不帶登入資訊)就不會被以 401 拒絕。

如果使用 AI 建置工具,請精確描述設定:

位於 https://myapp.com 的前端會呼叫位於 https://api.myapp.com 的 API,並收到「Response to preflight request doesn't pass access control check」。請在 API 上設定 CORS,只允許 https://myapp.com 與預覽來源,允許帶有 Content-Type 與 Authorization 標頭的 GET、POST、PATCH、DELETE,在驗證之前處理 OPTIONS,並且也在錯誤回應中加入 CORS 標頭。

步驟四:處理 Cookie 與憑證

如果你的前端要把 Cookie(例如登入工作階段)送到另一個來源,雙方都必須達成共識:

  • 前端在 fetch 上設定 credentials: "include",或在 axios 中設定 withCredentials: true。
  • 伺服器送出 Access-Control-Allow-Credentials: true。
  • 伺服器的 Access-Control-Allow-Origin 必須是確切的來源,而不能是 *。

跨站的 Cookie 還需要在 Cookie 上設定 SameSite=None; Secure,而且某些瀏覽器會更進一步限制第三方 Cookie。這也是另一個應該優先採用步驟五做法的理由。

步驟五:以同來源的架構避免 CORS

通常最乾淨的修正方式,就是完全不發出跨來源請求。讓 API 與頁面處於同一個來源,例如 https://myapp.com/api/...:

  • 在開發環境中,多數開發伺服器都能設定代理。Vite 的 server.proxy 設定可以把 /api 轉發到另一個連接埠上的後端,因此瀏覽器只會與一個來源通訊。
  • 在正式環境中,透過主機的 rewrite 或代理設定,把 /api 路由到後端;或者讓前端與 API 由同一台伺服器提供服務。

這樣,前端就能呼叫相對路徑,例如 fetch("/api/orders")。不會有跨來源請求、不會有跨來源的預檢,也不會有 Cookie 的麻煩。這也意味著你不需要寫死在預覽與正式環境之間會不同的 API 網域。

請把前端改為使用相對的 /api 路徑呼叫後端,而不是完整網址,並設定開發代理,讓 /api 轉發到 API 伺服器。然後移除任何我們不再需要的 CORS 設定。

不要這樣做

  • 在 fetch 中使用 mode: "no-cors"。 它並不會繞過任何東西;只會給你的程式碼一個空的、無法讀取的回應。
  • 會停用 CORS 的瀏覽器擴充功能。 它們只改變你自己的瀏覽器。每位訪客仍然會看到錯誤。
  • 在真實的 App 中使用公開的「CORS 代理」服務。 它們能讀取並修改經過的所有內容,而且可能會停止服務。
  • 在使用登入的 API 上使用 Access-Control-Allow-Origin: *。 無論如何它都無法與憑證一起運作,而且對私人資料來說是錯誤的預設值。
  • 在允許憑證的情況下,把任何 Origin 標頭原樣回傳。 這會讓任何網站代表你的使用者發出已登入的請求。請使用允許清單。

AI 建置 App 中的常見原因

情況修正方法
前端使用密鑰呼叫第三方 API把呼叫移到後端
在預覽中正常,在已發佈的網域上失敗把已發佈與自訂網域的來源加入允許清單,或改用相對的 /api 路徑
只有在 POST、PUT 或 DELETE 上出錯未處理預檢;加入 OPTIONS 處理與允許的標頭
加入登入功能後才出現錯誤憑證需要確切的來源與 Allow-Credentials
出現「CORS 錯誤」,但 Network 分頁顯示 500先修正伺服器錯誤
API 網址寫死為 localhost使用相對路徑,或依環境設定

Mythex 中的 CORS

當 Mythex 專案同時有網站與 API 伺服器時,兩者都會以服務的形式在同一個專案中執行,你可以在預覽中切換。請代理人讓前端透過相對的 /api 路徑呼叫後端,並在開發時透過代理轉發,就能避開大多數 CORS 問題。如果你確實需要跨來源呼叫,請把主控台中的確切錯誤與相關的來源提供給代理人。也要在已發佈的 App 上檢查結果,因為它的位址與預覽不同。請參考文件中的多服務應用程式與API 後端,也可以參考我們的除錯 AI 建置 App指南,以及前端與後端的差異。

CORS 檢查清單

  • 已閱讀完整的主控台錯誤與失敗請求的狀態碼
  • 已排除隱藏的 404 或 500
  • 使用密鑰的 API 由後端呼叫,而不是由瀏覽器呼叫
  • 伺服器以確切允許的來源送出 Access-Control-Allow-Origin
  • OPTIONS 預檢在驗證之前,以允許的方法與標頭回應
  • 錯誤回應中也包含 CORS 標頭
  • 憑證:確切來源、Allow-Credentials: true,以及正確的 Cookie 設定
  • 預覽、已發佈與自訂網域的來源都已涵蓋,或改用相對的 /api 路徑
  • 沒有使用 no-cors、公開代理或回傳任意來源的捷徑

常見問題

CORS 錯誤是什麼意思?

你的頁面從某個來源載入,卻嘗試讀取來自另一個來源的回應,而那個伺服器的回應中沒有包含允許存取的標頭。瀏覽器會阻止你的程式碼讀取該回應。修正方法在伺服器端,或在請求的路由方式上,而不是在前端程式碼中。

我可以從前端修正 CORS 錯誤嗎?

無法真正修正。像 mode: 'no-cors' 這樣的設定,只會讓你的程式碼看不到回應內容。正確的做法是讓伺服器送出正確的 Access-Control-Allow-Origin 標頭,或是從你自己的後端或同來源的代理伺服器呼叫 API。

Access-Control-Allow-Origin: * 安全嗎?

對於任何人都可以讀取的公開資料來說沒有問題,例如不需要登入的公開 API。但它不能與 Cookie 或其他憑證一起使用;對於私人 API,你應該列出確切允許的來源。

為什麼我的請求在 Postman 或 curl 中能運作,在瀏覽器中卻不行?

CORS 只由瀏覽器強制執行。Postman 和 curl 等工具並不套用它,因此請求在那裡可能成功,但當頁面的 JavaScript 發出同樣的請求時,卻會被阻擋。

什麼是預檢(preflight)請求?

對於非簡單的請求,例如 JSON 的 POST,或帶有 Authorization 標頭的請求,瀏覽器會先發送一個 OPTIONS 請求,詢問伺服器允許哪些方法與標頭。如果這個 OPTIONS 請求失敗或缺少正確的標頭,真正的請求就永遠不會被送出。

Mythex · Pricing · 用 AI 打造任何東西 · 網站與應用程式範本 · Mythex 比較 · AI 應用程式開發詞彙表 · 免費的 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 Українська اردو Tiếng Việt 简体中文 繁體中文(香港)