คู่มือ

วิธีแก้ error CORS (และทำไมมันถึงเกิดขึ้น)

error CORS หมายความว่าหน้าเว็บของคุณพยายามอ่านการตอบกลับจาก origin อื่น (โดเมน พอร์ต หรือโปรโตคอลที่ต่างกัน) และเซิร์ฟเวอร์นั้นไม่ได้ส่ง header ที่บอกว่าอนุญาต เบราว์เซอร์จึงบล็อกการตอบกลับเพื่อปกป้องผู้ใช้ คุณแก้ได้ที่เซิร์ฟเวอร์ โดยส่งคืน header Access-Control-Allow-Origin สำหรับ origin ของเว็บไซต์ และจัดการคำขอ preflight แบบ OPTIONS หรือหลีกเลี่ยงได้โดยส่งคำขอผ่าน back end ของคุณเองบน origin เดียวกัน

เป็นหนึ่งใน error ที่สับสนที่สุดในการพัฒนาเว็บ เพราะคำขอมักไปถึงเซิร์ฟเวอร์ได้ปกติ คู่มือนี้อธิบายว่าเกิดอะไรขึ้นและวิธีแก้แต่ละรูปแบบ

"origin" หมายความว่าอะไร

origin คือการรวมกันของโปรโตคอล โดเมน และพอร์ต:

URLOrigin
https://myapp.com/dashboardhttps://myapp.com
https://api.myapp.com/usershttps://api.myapp.com (ซับโดเมนต่างกัน จึงเป็น origin ต่างกัน)
http://localhost:5173http://localhost:5173
http://localhost:3000http://localhost:3000 (พอร์ตต่างกัน จึงเป็น origin ต่างกัน)

เบราว์เซอร์ทำตาม same-origin policy: JavaScript ในหน้าอ่านการตอบกลับจาก origin ของตัวเองได้อย่างอิสระ แต่อ่านจาก origin อื่นไม่ได้ CORS (Cross-Origin Resource Sharing) คือระบบที่ให้เซิร์ฟเวอร์เลือกเปิดรับ: "การตอบกลับจากฉันอนุญาตให้หน้าจาก origin เหล่านี้อ่านได้"

มีสองผลที่ทำให้หลายคนประหลาดใจ:

  • คำขอมักไปถึงเซิร์ฟเวอร์ CORS มักขวางโค้ดของคุณไม่ให้ อ่าน การตอบกลับ ไม่ได้ขวางเซิร์ฟเวอร์ไม่ให้รับคำขอ POST อาจถูกบันทึกแล้วแม้คุณจะเห็น error
  • มีเพียงเบราว์เซอร์เท่านั้นที่บังคับใช้ curl, Postman และโค้ดฝั่งเซิร์ฟเวอร์จะไม่สนใจ CORS เลย นั่นคือเหตุผลที่ "ใช้ได้ใน Postman" ไม่บอกอะไรเกี่ยวกับ CORS

ขั้นที่ 1: อ่าน error ตรงตามที่แสดง

เปิดเครื่องมือสำหรับนักพัฒนา (คลิกขวา → Inspect) ไปที่ Console แล้วอ่านข้อความเต็ม ข้อความของ Chrome บอกว่าคุณมีปัญหาแบบไหน:

ข้อความมีคำว่าสิ่งที่ผิด
No 'Access-Control-Allow-Origin' header is presentเซิร์ฟเวอร์ไม่ได้ส่ง header เลย หรือคำขอล้มเหลวก่อนถึงโค้ด CORS ของคุณ (เช่น 404 หรือ 500)
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied originเซิร์ฟเวอร์อนุญาต origin อื่นที่ไม่ใช่ของคุณ
Response to preflight request doesn't pass access control checkคำขอ OPTIONS แบบ preflight ล้มเหลว
...must not be the wildcard '*' when the request's credentials mode is 'include'คุณส่งคุกกี้ และเซิร์ฟเวอร์ตอบด้วย *
Request header field authorization is not allowedเซิร์ฟเวอร์ไม่ได้ระบุ header นั้นใน Access-Control-Allow-Headers

จากนั้นเปิดแท็บ Network หาคำขอที่ล้มเหลว (และคำขอ OPTIONS ก่อนหน้า) และดูสถานะ HTTP กับ header ของการตอบกลับ

ตรวจสถานะก่อน "error CORS" หลายครั้งเป็น error ฝั่งเซิร์ฟเวอร์ที่ปลอมตัวมา ถ้าเซิร์ฟเวอร์ crash หรือส่ง 404 การตอบกลับ error ของมักไม่มี header CORS เบราว์เซอร์จึงรายงานว่าเป็น CORS แทนที่จะเป็นปัญหาจริง แก้ 500 หรือ 404 ก่อน error CORS อาจหายไปเอง

ขั้นที่ 2: ตัดสินใจว่าควรเรียก API นั้นจากเบราว์เซอร์หรือไม่

ก่อนเพิ่ม header ให้ถามว่าเบราว์เซอร์ควรเรียก API นี้โดยตรงหรือไม่

  • API ของบุคคลที่สามที่ต้องใช้คีย์ลับ (OpenAI, Stripe secret key, บริการอีเมลส่วนใหญ่) ไม่ควรเรียกจากเบราว์เซอร์เลย เพราะคีย์จะมองเห็นได้โดยผู้เยี่ยมชมทุกคน ให้เรียกจาก back end แทน และปัญหา CORS จะหายไป เพราะคำขอระหว่างเซิร์ฟเวอร์ไม่อยู่ภายใต้ CORS ดูวิธีรักษาความปลอดภัยของ API key
  • API ของบุคคลที่สามที่คุณไม่ได้ควบคุม ที่ไม่ส่ง header CORS แก้จากฝั่งคุณไม่ได้ ให้เรียกจาก back end ของคุณ
  • API ของคุณเอง แก้ได้โดยตั้งค่าให้ถูกต้อง ดำเนินการต่อด้านล่าง

ขั้นที่ 3: แก้ที่เซิร์ฟเวอร์ของคุณ

เซิร์ฟเวอร์ต้องเพิ่ม header ที่ถูกต้องในการตอบกลับ รวมถึงการตอบกลับ error

สำหรับคำขอจริง ส่ง:

  • Access-Control-Allow-Origin: https://myapp.com (origin ตรงของ frontend ของคุณ ไม่มี slash ท้าย)
  • Vary: Origin ถ้าค่าขึ้นอยู่กับ origin ที่ขอ

สำหรับคำขอ preflight OPTIONS ตอบด้วย status สำเร็จ (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 (ทุก header แบบกำหนดเองที่คุณส่ง)
  • ตัวเลือก Access-Control-Max-Age: 600 เพื่อให้เบราว์เซอร์แคช preflight ไว้สักพัก

เฟรมเวิร์กส่วนใหญ่มีแพ็กเกจหรือการตั้งค่าที่ทำเรื่องนี้ได้ ใน 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 ก่อน route และการยืนยันตัวตน เพื่อให้คำขอ preflight (ซึ่งไม่มีการล็อกอิน) ไม่ถูกปฏิเสธด้วย 401

กับเครื่องมือสร้างด้วย AI ให้อธิบายการตั้งค่าอย่างละเอียด:

frontend ที่ https://myapp.com เรียก API ที่ https://api.myapp.com และได้ "Response to preflight request doesn't pass access control check" ตั้งค่า CORS บน API ให้อนุญาต https://myapp.com และ origin ของพรีวิวเท่านั้น อนุญาต GET, POST, PATCH, DELETE พร้อม header Content-Type และ Authorization จัดการ OPTIONS ก่อนการยืนยันตัวตน และใส่ header CORS ในการตอบกลับ error ด้วย

ขั้นที่ 4: จัดการคุกกี้และ credentials

ถ้า frontend ของคุณส่งคุกกี้ (เช่น session การล็อกอิน) ไปยัง origin อื่น ทั้งสองฝั่งต้องตกลงกัน:

  • frontend ตั้ง credentials: "include" ใน fetch หรือ withCredentials: true ใน axios
  • เซิร์ฟเวอร์ส่ง Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin ของเซิร์ฟเวอร์ต้องเป็น origin ตรง ไม่ใช่ *

คุกกี้ข้ามไซต์ยังต้องใช้ SameSite=None; Secure บนคุกกี้ และบางเบราว์เซอร์จำกัดคุกกี้ของบุคคลที่สามเพิ่มเติม นั่นเป็นอีกเหตุผลหนึ่งที่ควรเลือกวิธีในขั้นที่ 5

ขั้นที่ 5: หลีกเลี่ยง CORS ด้วยการตั้งค่า origin เดียวกัน

บ่อยครั้งวิธีแก้ที่สะอาดที่สุดคือไม่ทำคำขอข้าม origin เลย ให้บริการ API จาก origin เดียวกับหน้า เช่น https://myapp.com/api/...:

  • ในการพัฒนา เซิร์ฟเวอร์พัฒนาส่วนใหญ่ทำพร็อกซีได้ การตั้งค่า server.proxy ของ Vite ส่งต่อ /api ไปยัง back end บนพอร์ตอื่นได้ เบราว์เซอร์จึงคุยกับ origin เดียวเท่านั้น
  • ในโปรดักชัน route /api ไปยัง back end ผ่านการตั้งค่า rewrite หรือ proxy ของโฮสต์ หรือให้บริการ frontend และ API จากเซิร์ฟเวอร์เดียวกัน

จากนั้น frontend เรียกพาธแบบ relative เช่น fetch("/api/orders") ไม่มีคำขอข้าม origin ไม่มี preflight ข้าม origin และไม่มีปัญหาเรื่องคุกกี้ นอกจากนี้ยังหมายความว่าคุณไม่ต้องฝัง API domain ที่ต่างกันระหว่างพรีวิวกับโปรดักชัน

เปลี่ยน frontend ให้เรียก back end ด้วยพาธ /api แบบ relative แทน URL เต็ม และตั้ง dev proxy ให้ /api ส่งต่อไปยังเซิร์ฟเวอร์ API จากนั้นลบการตั้งค่า CORS ที่ไม่จำเป็นแล้วออก

สิ่งที่ไม่ควรทำ

  • mode: "no-cors" ใน fetch มันไม่ได้ข้ามอะไรเลย แต่ให้โค้ดของคุณได้การตอบกลับที่ว่างเปล่าและอ่านไม่ได้
  • ส่วนเสริมของเบราว์เซอร์ที่ปิด CORS มันเปลี่ยนแค่เบราว์เซอร์ของคุณ ผู้เยี่ยมชมทุกคนยังเห็น error
  • บริการ "CORS proxy" สาธารณะ สำหรับแอปจริง เพราะมันอ่านและเปลี่ยนทุกอย่างที่ผ่านมันได้ และมันล่มได้
  • Access-Control-Allow-Origin: * บน API ที่ใช้การล็อกอิน มันใช้กับ credentials ไม่ได้อยู่แล้ว และเป็นค่าเริ่มต้นที่ผิดสำหรับข้อมูลส่วนตัว
  • สะท้อน Origin ใด ๆ กลับไปพร้อมอนุญาต credentials ทำให้เว็บไซต์ใดก็ได้ส่งคำขอที่ล็อกอินแล้วในนามของผู้ใช้ของคุณ ให้ใช้ allowlist

สาเหตุที่พบบ่อยในแอปที่สร้างด้วย AI

สถานการณ์วิธีแก้
frontend เรียก API ของบุคคลที่สามด้วยคีย์ลับย้ายการเรียกไปยัง back end
ทำงานในพรีวิว แต่ล้มเหลวบนโดเมนที่เผยแพร่เพิ่ม origin ของโดเมนที่เผยแพร่และโดเมนที่กำหนดเองลงใน allowlist หรือใช้พาธ /api แบบ relative
error เกิดเฉพาะกับ POST, PUT หรือ DELETEไม่ได้จัดการ preflight เพิ่มการจัดการ OPTIONS และ header ที่อนุญาต
error เกิดหลังจากเพิ่มการล็อกอินcredentials ต้องใช้ origin ตรงและ Allow-Credentials
"error CORS" แต่แท็บ Network แสดง 500แก้ error ของเซิร์ฟเวอร์ก่อน
URL ของ API ถูกฝังเป็น localhostใช้พาธ relative หรือการตั้งค่าต่อสภาพแวดล้อม

CORS ใน Mythex

เมื่อโปรเจกต์ Mythex มีเว็บไซต์และเซิร์ฟเวอร์ API ทั้งสองจะรันเป็นบริการในโปรเจกต์เดียวกัน และคุณสลับระหว่างกันในพรีวิวได้ การขอให้เอเจนต์ทำให้ frontend เรียก back end ผ่านพาธ /api แบบ relative โดยพร็อกซีในการพัฒนาจะหลีกเลี่ยงปัญหา CORS ส่วนใหญ่ ถ้าคุณจำเป็นต้องเรียกข้าม origin จริง ๆ ให้ส่ง error ที่แน่นอนจาก console และ origin ที่เกี่ยวข้องให้เอเจนต์ และตรวจผลลัพธ์บนแอปที่เผยแพร่ด้วย เพราะที่อยู่ของมันต่างจากพรีวิว ดูMulti-service apps และAPI backends ในเอกสาร และคู่มือของเราเรื่องการดีบักแอปที่สร้างด้วย AI และfrontend กับ backend

เช็กลิสต์ CORS

  • อ่าน error เต็มใน console และ status code ของคำขอที่ล้มเหลว
  • ตัดความเป็นไปได้ของ 404 หรือ 500 ที่ซ่อนอยู่ออกแล้ว
  • API ที่ใช้คีย์ลับถูกเรียกจาก back end ไม่ใช่เบราว์เซอร์
  • เซิร์ฟเวอร์ส่ง Access-Control-Allow-Origin พร้อม origin ที่อนุญาตแบบตรง
  • ตอบ preflight OPTIONS ด้วย method และ header ที่อนุญาต ก่อนการยืนยันตัวตน
  • มี header CORS ในการตอบกลับ error ด้วย
  • credentials: origin ตรง, Allow-Credentials: true และการตั้งค่าคุกกี้ที่ถูกต้อง
  • ครอบคลุม origin ของพรีวิว โปรดักชัน และโดเมนที่กำหนดเอง หรือใช้พาธ /api แบบ relative
  • ไม่มี no-cors พร็อกซีสาธารณะ หรือทางลัดที่สะท้อนทุก origin

คำถาม

error CORS หมายความว่าอะไร?

หน้าของคุณที่โหลดจาก origin หนึ่งพยายามอ่านการตอบกลับจาก origin อื่น และเซิร์ฟเวอร์นั้นไม่ได้ส่ง header ที่อนุญาตให้ทำได้ เบราว์เซอร์จึงบล็อกไม่ให้โค้ดของคุณอ่านการตอบกลับ วิธีแก้อยู่ที่เซิร์ฟเวอร์ หรือวิธีที่คำขอถูก route ไม่ใช่ในโค้ด frontend

แก้ error CORS จาก frontend ได้ไหม?

ไม่ได้จริง ๆ การตั้งค่าอย่าง mode: 'no-cors' เพียงซ่อนการตอบกลับจากโค้ดของคุณ วิธีที่ถูกต้องคือให้เซิร์ฟเวอร์ส่ง header Access-Control-Allow-Origin ที่ถูกต้อง หรือเรียก API จาก back end ของคุณเองหรือพร็อกซีที่อยู่ origin เดียวกัน

Access-Control-Allow-Origin: * ปลอดภัยไหม?

ใช้ได้กับข้อมูลสาธารณะที่ใครก็อ่านได้ เช่น API สาธารณะที่ไม่ต้องล็อกอิน แต่ใช้กับคุกกี้หรือ credential อื่นไม่ได้ และสำหรับ API ส่วนตัวควรระบุ origin ที่อนุญาตให้ชัดเจน

ทำไมคำขอทำงานใน Postman หรือ curl แต่ไม่ทำงานในเบราว์เซอร์?

CORS ถูกบังคับใช้โดยเบราว์เซอร์เท่านั้น เครื่องมืออย่าง Postman และ curl ไม่ได้บังคับใช้ ดังนั้นคำขอจึงสำเร็จที่นั่นได้ และยังถูกบล็อกเมื่อ JavaScript ของหน้าส่งคำขอเอง

preflight request คืออะไร?

สำหรับคำขอที่ไม่ใช่แบบง่าย เช่น POST ที่เป็น JSON หรือคำขอที่มี header Authorization เบราว์เซอร์จะส่งคำขอ OPTIONS ก่อน เพื่อถามเซิร์ฟเวอร์ว่าอนุญาต method และ header ใดบ้าง ถ้าคำขอ OPTIONS นั้นล้มเหลวหรือไม่มี header ที่ถูกต้อง คำขอจริงจะไม่ถูกส่งออกไปเลย

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 简体中文 繁體中文(香港) 繁體中文(台灣)