CORS
ถ้ามี JavaScript บน browser ที่อยู่คนละ origin เรียก API ของคุณ คุณจะได้เจอกับ CORS แน่นอน นี่คือเรื่องที่คนเข้าใจผิดกันมากที่สุดเรื่องหนึ่งของ web API เรามาทำความเข้าใจให้ชัดกันว่า CORS คืออะไร และไม่ใช่อะไร
CORS ทำหน้าที่อะไรกันแน่
หัวข้อที่มีชื่อว่า “CORS ทำหน้าที่อะไรกันแน่”same-origin policy ของ browser จะบล็อกหน้าเว็บบน https://app.example.com ไม่ให้อ่าน response จาก https://api.example.com เว้นแต่ API จะอนุญาตอย่างชัดเจนผ่าน header ของ Cross-Origin Resource Sharing ตัวที่บังคับใช้ CORS คือ browser ซึ่งทำแทน user ไม่ใช่การควบคุมการเข้าถึงฝั่ง server
simple request และ preflight
หัวข้อที่มีชื่อว่า “simple request และ preflight”สำหรับ request แบบ “non-simple” (ส่วนใหญ่คือ JSON POST/PUT/DELETE และ header ที่กำหนดเอง) browser จะส่ง request preflight แบบ OPTIONS ไปก่อนเพื่อขออนุญาต:
sequenceDiagram participant B as Browser participant S as API B->>S: OPTIONS /articles (preflight) S-->>B: 204 + Access-Control-Allow-Origin/Methods/Headers B->>S: POST /articles (actual request) S-->>B: 201 Created + Access-Control-Allow-Origin
header ใน response ที่สำคัญ: Access-Control-Allow-Origin (origin ใดบ้างที่อ่าน response ได้), -Allow-Methods, -Allow-Headers และ -Allow-Credentials (อนุญาตให้ส่ง cookie/credentials หรือไม่)
การตั้งค่า CORS
หัวข้อที่มีชื่อว่า “การตั้งค่า CORS”จงหลีกเลี่ยงความผิดพลาดคลาสสิกของการสะท้อน origin ใดก็ได้ (Access-Control-Allow-Origin: *) ควบคู่กับ Allow-Credentials: true — spec ห้ามใช้คู่กันอยู่แล้ว และที่สำคัญกว่านั้นคือเท่ากับเปิดเผย response ที่ผ่าน authentication ให้เว็บไซต์ใดก็ได้
| CORS Header | ความหมาย | ตัวอย่าง |
|---|---|---|
Access-Control-Allow-Origin | origin ที่อนุญาต | https://app.example.com หรือ * |
Access-Control-Allow-Methods | HTTP method ที่อนุญาต | GET, POST, PATCH, DELETE |
Access-Control-Allow-Headers | header ที่ client ส่งได้ | Content-Type, Authorization |
Access-Control-Max-Age | cache preflight นานแค่ไหน | 86400 (1 วัน) |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”Access-Control-Allow-Origin: * กับ Credentials
อาการ:
- ตั้ง
Access-Control-Allow-Origin: *แต่ต้องการส่ง cookie หรือ Authorization header - browser block request —
*ไม่ work กับ credentials - ต้องระบุ origin จริง เช่น
https://app.example.comเมื่อใช้ credentials
Preflight ทุก Request เพราะ Cache ไม่ได้ อาการ:
- ไม่ตั้ง
Access-Control-Max-Age— browser ส่ง OPTIONS preflight ทุกครั้ง - เพิ่ม latency ทุก request
- ตั้ง
Access-Control-Max-Age: 86400เพื่อ cache preflight 1 วัน
💡 ตัวอย่างจากของจริง
GitHub API:
- CORS ที่อนุญาตเฉพาะ origin ที่ register ผ่าน OAuth App
- ไม่อนุญาต
*สำหรับ authenticated endpointStripe.js:
- browser SDK เรียก Stripe API โดยตรง — CORS config อนุญาตเฉพาะ Stripe domain
- API Key ที่ใช้ใน browser เป็น publishable key เท่านั้น (ไม่มีสิทธิ์ charge)