ทำความรู้จัก CORS ปัญหาคลาสสิกที่โปรแกรมเมอร์มือใหม่ต้องเจอ
เวลาเราเขียนโปรแกรมฝั่งหน้าบ้าน (Frontend) เพื่อดึงข้อมูลจาก API (ช่องทางเชื่อมต่อข้อมูลระหว่างโปรแกรม) ของเราเอง หลายคนมักจะเจอกับข้อความสีแดงๆ ใน Console (หน้าต่างแสดงผลลัพธ์และแจ้งเตือนของเบราว์เซอร์) ว่ามีข้อผิดพลาดเรื่อง CORS ซึ่งทำให้โปรแกรมของเราทำงานต่อไม่ได้ ทั้งที่ API ก็ดูเหมือนจะทำงานปกติ
CORS ย่อมาจาก Cross-Origin Resource Sharing (กลไกที่เบราว์เซอร์ใช้ควบคุมการขอข้อมูลข้ามแหล่งที่มา) มันไม่ใช่บั๊กของโค้ดคุณ แต่มันคือระบบรักษาความปลอดภัยที่ Browser (โปรแกรมท่องเว็บอย่าง Chrome หรือ Firefox) สร้างขึ้นมาเพื่อป้องกันไม่ให้เว็บไซต์แปลกปลอมมาขโมยข้อมูลของคุณผ่านการใช้งานเบราว์เซอร์ของผู้ใช้โดยไม่ได้รับอนุญาต
ลองนึกภาพว่าคุณล็อกอินเข้าเว็บธนาคารอยู่ แล้วคุณดันเผลอไปกดลิงก์เว็บอันตราย เว็บนั้นอาจพยายามแอบส่งคำสั่งไปที่ API ของธนาคารเพื่อโอนเงินของคุณ ถ้าไม่มีระบบนี้ เบราว์เซอร์ก็จะยอมให้เว็บนั้นสั่งงานแทนคุณทันที ดังนั้นการที่เบราว์เซอร์บล็อกการเชื่อมต่อ จึงเป็นการทำหน้าที่ปกป้องความปลอดภัยให้คุณนั่นเอง
Origin คืออะไร ทำไมเบราว์เซอร์ถึงต้องแยกแยะ
คำว่า Origin (จุดกำเนิดของเว็บไซต์) ในทางเทคนิคคือการรวมกันของ 3 ส่วนหลัก ได้แก่ Protocol (โปรโตคอลหรือรูปแบบการส่งข้อมูลเช่น http หรือ https), Domain (ชื่อเว็บไซต์) และ Port (ช่องทางรับส่งข้อมูลในคอมพิวเตอร์) หากส่วนใดส่วนหนึ่งไม่ตรงกัน เบราว์เซอร์จะถือว่ามันเป็นคนละแหล่งที่มาทันที
สมมติว่าแอปหน้าบ้านของคุณรันอยู่ที่ http://localhost:3000 แล้ว API ของคุณรันอยู่ที่ http://localhost:5000 แม้จะอยู่บนเครื่องเดียวกัน แต่เบราว์เซอร์มองว่าเลข Port ที่ต่างกันคือคนละ Origin กัน ทำให้เกิดปัญหา CORS ทันทีที่คุณพยายามดึงข้อมูลข้ามไปมาระหว่างสองที่นี้
นี่คือตัวอย่างการเปรียบเทียบแหล่งที่มาว่าแบบไหนคือ "แหล่งเดียวกัน" หรือ "คนละแหล่ง":
http://localhost:3000กับhttp://localhost:3000/apiถือเป็นแหล่งเดียวกันhttp://localhost:3000กับhttp://localhost:5000ถือเป็นคนละแหล่ง (เพราะ Port ต่างกัน)http://example.comกับhttps://example.comถือเป็นคนละแหล่ง (เพราะ Protocol ต่างกัน)
ทำไมต้องมี Preflight Request ก่อนเริ่มส่งข้อมูลจริง
ในการส่งคำสั่งบางประเภท เช่น POST (การส่งข้อมูลใหม่ไปเก็บที่เซิร์ฟเวอร์) หรือการใช้ Header (ข้อมูลส่วนขยายที่ส่งไปกับคำขอ) พิเศษ เบราว์เซอร์จะฉลาดพอที่จะไม่ส่งข้อมูลไปตรงๆ ทันที แต่จะส่งคำสั่งที่เรียกว่า Preflight Request (การส่งคำขอตรวจสอบสิทธิ์ล่วงหน้า) ไปถามเซิร์ฟเวอร์ก่อนว่า "อนุญาตให้ Origin นี้ใช้งานได้ไหม"
คำสั่งที่ใช้ในการตรวจสอบนี้คือ OPTIONS ซึ่งเบราว์เซอร์จะส่งออกไปอัตโนมัติ หากเซิร์ฟเวอร์ตอบกลับมาว่า "อนุญาต" ด้วยการแนบ Header ที่ถูกต้องมาให้ เบราว์เซอร์ถึงจะยอมให้ส่งคำสั่งหลักตามไป ถ้าเซิร์ฟเวอร์ไม่ได้ตั้งค่าไว้ เบราว์เซอร์ก็จะตัดจบการทำงานและแจ้งเตือนทันที
ตัวอย่างการตั้งค่าใน ASP.NET Core เพื่ออนุญาตให้ Frontend เข้าถึง API ได้:
// ตั้งค่านโยบาย CORS ในไฟล์ Program.cs
builder.Services.AddCors(options => {
options.AddPolicy("AllowMyFrontend", policy => {
policy.WithOrigins("http://localhost:3000") // ระบุ Origin ที่อนุญาต
.AllowAnyHeader() // อนุญาตให้ส่งข้อมูลส่วนขยายได้ทุกตัว
.AllowAnyMethod(); // อนุญาตให้ใช้คำสั่ง GET, POST, PUT, DELETE
});
});
app.UseCors("AllowMyFrontend"); // เรียกใช้งานนโยบายที่สร้างไว้
อธิบายโค้ด: บรรทัดที่ 3 คือการสร้างกฎชื่อ "AllowMyFrontend" บรรทัดที่ 4 คือการระบุว่าอนุญาตเฉพาะเว็บจาก localhost:3000 เท่านั้น บรรทัดที่ 5-6 คือการอนุญาตให้ส่ง Header และ Method ได้อิสระ ส่วนบรรทัดที่ 9 คือการสั่งให้ API ของเรานำกฎนี้ไปบังคับใช้จริง
ผลลัพธ์ที่ได้: เมื่อ Frontend ส่งคำขอจาก http://localhost:3000 ไปยัง API เบราว์เซอร์จะได้รับ Header ตอบกลับเป็น Access-Control-Allow-Origin: http://localhost:3000 ทำให้การเชื่อมต่อสำเร็จและดึงข้อมูลได้ปกติ
ความเข้าใจผิดที่พบบ่อยระหว่าง CORS กับ Authentication
โปรแกรมเมอร์มือใหม่หลายคนมักสับสนว่า CORS คือระบบรักษาความปลอดภัยแบบเดียวกับ Authentication (การตรวจสอบตัวตนผู้ใช้งาน) ซึ่งจริงๆ แล้วมันคนละเรื่องกันเลย การที่ CORS อนุญาตให้เข้าถึง API ได้ ไม่ได้แปลว่าทุกคนจะเข้ามาดูข้อมูลลับของคุณได้ถ้าคุณไม่ได้ทำระบบตรวจสอบสิทธิ์ไว้
CORS ทำหน้าที่แค่ "เบราว์เซอร์จะยอมให้เว็บหน้าบ้านเว็บนี้ คุยกับ API ตัวนี้ไหม" เท่านั้น ส่วน Authentication ทำหน้าที่ "คนที่ใช้งานอยู่คือใคร และมีสิทธิ์เห็นข้อมูลนี้ไหม" คุณสามารถตั้งค่า CORS ให้คนอื่นเข้าถึง API ได้ แต่ใน API ก็ยังต้องบังคับให้ล็อกอินด้วย Token (รหัสผ่านชั่วคราว) อยู่ดี
หากคุณเจอข้อผิดพลาด CORS สิ่งที่ควรทำไม่ใช่การปิดระบบความปลอดภัย แต่คือการตรวจสอบว่าคุณระบุ Origin ถูกต้องหรือไม่ ดังนี้:
- ตรวจสอบว่า URL ของ Frontend ตรงกับที่ตั้งค่าใน API หรือไม่
- เช็คใน Network Tab (ส่วนแสดงการรับส่งข้อมูลในเบราว์เซอร์) ว่ามีการส่ง
OPTIONSrequest แล้วผลตอบกลับเป็น 200 หรือไม่ - ดูว่า Header ชื่อ
Access-Control-Allow-Originส่งกลับมาจากเซิร์ฟเวอร์จริงหรือไม่
เลิกใช้ AllowAnyOrigin() ในงานที่ต้องใช้งานจริง
หลายคนแก้ปัญหาด้วยการใส่ .AllowAnyOrigin() (การอนุญาตจากทุกแหล่งที่มา) เพื่อให้มันหายแดงและทำงานได้ทันที แต่นี่คือความเสี่ยงด้านความปลอดภัยอย่างมาก เพราะการเปิดให้ทุกคนเข้าถึง API ได้โดยไม่ตรวจสอบ Origin อาจทำให้เว็บไซต์อันตรายแอบดึงข้อมูลจาก API ของคุณไปใช้ได้
ในระหว่างการพัฒนาบนเครื่องตัวเอง (Development) อาจจะพอทำได้เพื่อความรวดเร็ว แต่เมื่อต้อง Deploy (นำโค้ดขึ้นเซิร์ฟเวอร์จริง) คุณต้องเปลี่ยนมาใช้รายการ Domain ที่ชัดเจนเสมอ การกำหนดเฉพาะเจาะจงจะช่วยให้คุณควบคุมได้ว่าใครบ้างที่มีสิทธิ์เรียกใช้บริการจาก API ของคุณ
สรุปวิธีแก้ปัญหาที่ถูกต้องสำหรับมือใหม่:
- ตรวจสอบ URL ของ Frontend ให้ละเอียดว่า http หรือ https และมีเลข Port หรือไม่
- หากต้องรับหลาย Domain ให้สร้าง List (รายการ) ของ Allowed Origins ไว้ในไฟล์ตั้งค่า
- อย่าลืมเช็คว่า Middleware (ตัวกลางที่จัดการคำขอก่อนถึงโค้ดหลัก) ของ CORS วางไว้ในลำดับที่ถูกต้องในโค้ด
สรุป: รับมือกับ CORS อย่างมือโปร
CORS ไม่ใช่ศัตรูของคุณ แต่เป็นเครื่องมือที่ช่วยปกป้องผู้ใช้งานจากการถูกโจมตีผ่านเบราว์เซอร์ การทำความเข้าใจว่ามันทำงานอย่างไร จะช่วยให้คุณประหยัดเวลาในการแก้บั๊กได้มหาศาล แทนที่จะสุ่มแก้โค้ดไปเรื่อยๆ ให้ลองเช็คที่ Origin และ Header เป็นอันดับแรกเสมอ
สำหรับคนที่กำลังฝึกเขียนโปรแกรม การเจอ CORS คือโอกาสดีที่จะได้เรียนรู้เรื่องระบบเครือข่ายและความปลอดภัยพื้นฐาน ลองฝึกตั้งค่า CORS ในโปรเจกต์จำลองของคุณเอง แล้วลองทดสอบด้วยการเปลี่ยน Domain หน้าบ้านดูว่าเบราว์เซอร์จะบล็อกอย่างไร นี่จะทำให้คุณเข้าใจหัวใจสำคัญของมันได้ดีกว่าการอ่านตำราเล่มไหน
จงจำไว้ว่าการแก้ปัญหาที่ดีที่สุด คือการเข้าใจต้นตอของมัน อย่าเพียงแค่ทำให้โปรแกรมหายแดง แต่ต้องรู้ว่าทำไมมันถึงแดงตั้งแต่แรก เพราะนั่นคือทักษะที่แยกโปรแกรมเมอร์มือสมัครเล่นออกจากมืออาชีพครับ
ที่มา: CORS Explained: Why Your Browser Blocks Your API — DEV Community