

เวลาแยกหน้าเว็บกับ API คนละที่ เราอาจลองเรียก API ด้วยเครื่องมือทดสอบแล้วเห็นคำตอบปกติ แต่พอเรียกจากหน้าเว็บ browser กลับแจ้ง CORS จน JavaScript อ่านข้อมูลไม่ได้ ถ้าเห็นว่าฝั่งบริการเปิดได้ก็อาจสงสัยว่าคำขอของหน้าเว็บส่งไปไม่ถึงเลยหรือเปล่า
อาการนี้ต้องแยกสองเรื่องออกจากกัน คือคำขอถูกส่งหรือประมวลผลแล้วหรือยัง กับ browser อนุญาตให้ script ของหน้านั้นอ่านคำตอบได้หรือไม่ สำหรับคำขอบางแบบ สิ่งแรกอาจเกิดขึ้นแล้วแม้สิ่งหลังถูกปฏิเสธ
ก่อนเพิ่ม header เพื่อแก้อาการ จึงควรดูขอบเขตของ origin และเส้นทางคำขอจริงก่อนครับ จะได้แก้กติกาที่เกี่ยวข้องโดยยังรักษาการตรวจสิทธิ์ของ API ตามเดิม
หน้าเว็บกับ API อาจดูเหมือนอยู่เครื่องเดียวกันสำหรับคนพัฒนา แต่ถ้าใช้ port ต่างกันก็เป็นคนละ origin ได้ เช่น localhost ที่พอร์ตหนึ่งกับอีกพอร์ตหนึ่ง หรือหน้า HTTPS ที่เรียก API ผ่าน HTTP ก็ไม่ได้อยู่ในบริบทเดียวกันเพียงเพราะชื่อเครื่องคล้ายกัน
ควรจด origin ของหน้าที่เปิดจริงกับ URL เป้าหมาย รวมถึงการ redirect ที่เกิดขึ้น ไม่ใช้ค่าที่จำได้จากไฟล์ config เพียงอย่างเดียว เพราะปลายทางที่ browser ติดต่ออาจเปลี่ยนหลังผ่าน proxy หรือเส้นทาง login ไปแล้ว
CORS ใช้ให้บริการบอกว่า origin ใดอ่านข้อมูลตอบกลับผ่าน browser ได้ตามกติกา จึงเกี่ยวกับบริบทของหน้าที่เรียก ไม่ใช่รายชื่อ IP เครื่องผู้ใช้ และไม่ควรถูกตีความเป็นระบบยืนยันตัวตนแทน session หรือ token ของคำขอ

คำขอข้าม origin ที่เข้าเงื่อนไขเรียบง่ายตามมาตรฐานอาจถูกส่งออกไปได้ก่อน จากนั้น browser ตรวจ header ของคำตอบว่าจะเปิดให้ JavaScript อ่านหรือไม่ หากไม่ผ่าน script อาจเห็นข้อผิดพลาดทั้งที่ server เคยรับคำขอนั้นแล้ว
ส่วนคำขอที่ต้อง preflight จะมี OPTIONS มาก่อนเพื่อถามเรื่อง method กับ header ที่ต้องการใช้ เช่น งานส่ง JSON หรือใช้ header บางชนิดตามเงื่อนไข CORS หากขั้นนี้ไม่ผ่าน browser จะไม่ส่งคำขอจริงตามเส้นทางนั้น
ตอนตรวจจึงต้องดูว่าใน Network มีแค่ OPTIONS หรือมี POST GET หรือ method จริงตามมาด้วย ไม่เรียกทุกข้อความ CORS ว่า API ไม่ถูกเรียก เพราะอาจนำไปสู่การ retry งานที่ฝั่งบริการทำผลไปแล้วโดยยังไม่รู้
Preflight ปกติไม่ได้แนบ session cookie ไปให้บริการเหมือนคำขอใช้งานจริง หากชั้นที่รับ OPTIONS กำลังรอข้อมูล login แบบเดียวกันอยู่ ต้องตรวจการวาง middleware กับกติกาตอบ CORS ให้ตรงตามหน้าที่ ไม่สรุปจากการไม่มี cookie ในขั้นนี้ว่า browser ส่ง session เสียทั้งหมด
การตอบว่า origin method และ header ที่ขอได้รับอนุญาต ยังไม่อนุญาตให้คำขอจริงทำงานกับข้อมูลได้เอง ฝั่ง API ต้องตรวจตัวตน สิทธิ์และ input ตามปกติเมื่อคำขอจริงเข้ามา การแยกสองหน้าที่นี้ช่วยไม่ให้การแก้ preflight ไปเปิดการเข้าถึงข้อมูลเกินขอบเขต
ถ้ามี gateway กับแอปอยู่หลายชั้น ควรรู้ว่าใครตอบ OPTIONS และใครตอบ error ของคำขอจริง บางครั้งแก้ header ในแอปแล้วคำตอบที่ browser ได้กลับมาจากอีกชั้นหนึ่ง การตรวจแหล่งของ response จึงสำคัญกว่าการเพิ่มค่าเดิมซ้ำหลายจุด

Access-Control-Allow-Origin ใช้ระบุ origin ที่อ่านคำตอบได้ตามกติกา หากคำขอใช้ credentials ตามรูปแบบที่เกี่ยวข้อง การตอบ wildcard ไม่สามารถใช้แทน origin ที่ต้องระบุชัดได้ และยังมีเงื่อนไขเรื่อง Access-Control-Allow-Credentials ประกอบ
ควรกำหนด origin ที่ตั้งใจให้ใช้งานจริงและตรวจค่าเข้ามาตามนโยบาย ไม่รับ Origin ใดก็ได้แล้วสะท้อนกลับเพื่อให้คำขอทุกแบบผ่านโดยยังไม่รู้ว่าเว็บใดควรอ่านข้อมูล ส่วนรายการ method กับ header ก็ควรสัมพันธ์กับสิ่งที่ API ต้องรองรับ
หากบริการเลือกคำตอบตาม Origin ต้องตรวจ cache ที่เกี่ยวข้องด้วย เช่น การแยกคำตอบด้วย Vary ตามเงื่อนไข เพื่อไม่ให้คำตอบที่จัดไว้สำหรับ origin หนึ่งถูกนำมาใช้กับอีก origin โดยไม่ตรงกติกา การเห็น header ถูกจากการเรียกครั้งเดียวจึงอาจยังไม่ครอบคลุมเส้นทางผ่าน cache
การตั้ง fetch เป็น no-cors อาจทำให้เราไม่เห็นข้อผิดพลาดแบบที่คาดไว้ แต่คำตอบที่ได้ในกรณี opaque จะไม่เปิด header กับ body ให้ JavaScript อ่านตามปกติ จึงไม่ใช่วิธีแก้สำหรับหน้าที่ต้องใช้ข้อมูล JSON จาก API นั้น
ถ้าตั้งใจให้ browser อ่านผล ต้องจัดความสัมพันธ์ระหว่างหน้าเว็บกับบริการให้ถูก เช่น ตอบ CORS ตามนโยบาย หรือใช้เส้นทางบริการที่ออกแบบรับผิดชอบคำขอไว้จริง ไม่เปลี่ยนโหมดเพียงเพื่อให้ข้อความใน console หายแล้วถือว่ารับข้อมูลได้ครบแล้ว
การที่เครื่องมือ command line หรือ server อีกตัวเรียกได้ก็ไม่ได้หักล้างการทำงานของ CORS เพราะมันไม่ได้อยู่ภายใต้กติกาอ่านข้อมูลของ script ใน browser แบบเดียวกัน ด้วยเหตุนี้ API ยังต้องมีการตรวจสิทธิ์ของตัวเอง ไม่ใช้ CORS เป็นกำแพงเดียวสำหรับคนที่เรียกตรงจากแหล่งอื่น
เริ่มจาก origin กับ URL ที่ browser ใช้จริง แล้วตรวจ preflight ถ้ามี ต่อด้วย method จริง status และ header ของคำตอบ รวมถึง error ที่ proxy สร้างขึ้น การดู log ฝั่งบริการร่วมกันช่วยยืนยันได้ว่าคำขอมาถึงแล้วและทำผลถึงขั้นไหน โดยไม่ต้องพิมพ์ token หรือข้อมูลส่วนตัวออกมาเพื่อ debug
กรณีทดสอบควรครอบทั้งการตอบปกติ การปฏิเสธสิทธิ์และบริการผิดพลาด เพราะ response ในแต่ละเส้นทางอาจออกจากคนละส่วน หากผ่านเฉพาะ200แต่พอ401หรือ500อ่านรายละเอียดไม่ได้ ก็ยังต้องตรวจการตอบในเส้นทางเหล่านั้นตามนโยบายเดียวกัน
CORS ต้องอ่านจาก origin ขั้น preflight และการเปิดให้ script อ่านคำตอบร่วมกัน เมื่อรู้ว่าคำขอจริงไปถึงหรือยังและใครเป็นคนตอบ header เราจะเลือกแก้ได้ตรงชั้น พร้อมรักษาการยืนยันตัวตนของ API ไว้ตามหน้าที่ การเรียกผ่านจากนอก browser จึงเป็นหลักฐานหนึ่งของบริการ แต่ยังไม่แทนการตรวจเส้นทางที่หน้าเว็บใช้งานจริง