如何修正 CORS 錯誤(以及為什麼會發生)
CORS 錯誤代表你的網頁嘗試讀取來自不同來源(另一個網域、連接埠或通訊協定)的回應,而該伺服器沒有傳送允許存取的標頭。為了保護使用者,瀏覽器會阻擋這個回應。修正方法是在伺服器端進行處理:為你網站的來源回傳 Access-Control-Allow-Origin 標頭,並處理 OPTIONS 預檢請求;或者,透過你自己在同一來源下的後端發送請求,從而避開這個問題。
這是網頁開發中最令人困惑的錯誤之一,因為請求往往能順利到達伺服器。本指南會說明發生了什麼事,以及如何修正每一種變體。
「來源」是什麼意思
來源是通訊協定、網域與連接埠的組合:
| 網址 | 來源 |
|---|---|
https://myapp.com/dashboard | https://myapp.com |
https://api.myapp.com/users | https://api.myapp.com(子網域不同,因此來源不同) |
http://localhost:5173 | http://localhost:5173 |
http://localhost:3000 | http://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 check | OPTIONS 預檢請求失敗 |
...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.comAccess-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETEAccess-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 請求失敗或缺少正確的標頭,真正的請求就永遠不會被送出。