CORS ย่อมาจาก Cross-Origin Resource Sharing
Origin คือ header ตัวนึง ที่บอกว่า request ถูกเรียกจากที่ไหน จะต่างกับ Referer โดยที่ Origin จะไม่มี path มาด้วย
Origin ประกอบด้วย 3 ส่วน คือ
ถ้าเข้าเว็บจาก file:// Origin จะเป็น null
Origin: null
Origin: <scheme>://<hostname>[:<port>]
http://example.com กับ https://example.com เป็นคนละ Origin กัน เพราะ scheme ไม่เหมือนกัน คือ http กับ httpshttp://example.com กับ http://www.example.com เป็นคนละ Origin กัน เพราะ hostname ไม่เหมือนกัน คือ example.com กับ www.example.comhttp://example.com:3000 กับ http://example.com:8080 เป็นคนละ Origin กัน เพราะ port ไม่เหมือนกัน คือ 3000 กับ 8000http://example.com กับ http://example.com:80 เป็น Origin เดียวกัน เพราะ http ถ้าไม่มี port จะถือว่าเป็น port 80 RFC 6454 3.2.1เมื่อเราต้องการ fetch resource จาก origin นึง ไปอีก origin นึง จะเกิดการเรียก resource ข้าม origin กัน
Browser จะส่ง OPTIONS request ไปถาม server อีก origin ก่อน ว่าจะอนุญาตให้ origin ต้นทางสามารถเข้าไปเรียก resource ได้ไหม
หมายความว่า ถ้า browser ไม่ support CORS ก็จะสามารถเรียกได้เลย โดยไม่ติด CORS หรือ request เกิดจาก Server-Server คุยกัน ก็จะไม่มี CORS เพราะ คนส่ง CORS คือ Browser
ตัวอย่างเช่น
ถ้าเราอยู่ที่ https://example.com แล้วต้องการส่ง request ไปที่ https://api.example.com
fetch('https://api.example.com/api1', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'key-1'
},
body: JSON.stringify(data)
})
สิ่งที่เกิดขึ้นคือ Browser จะส่ง preflight request ไปถามที่ api.example.com ก่อนว่า
https://example.com ส่ง request ไปไหม ?POST ไปไหม ?credentials: 'include') ไปไหม ?Content-Type กับ X-Api-Key ไปไหม ?OPTIONS /api1 HTTP/1.1
Host: api.example.com
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, X-Api-Key
ถ้า Server เราจะต้องการจะตอบ Browser ว่า
https://example.com ส่ง request เข้ามาได้GET กับ POST เข้ามาได้Content-Type กับ X-Api-Key เข้ามาได้Server จะต้องตอบ Browser กลับไปว่า
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, X-Api-Key
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
Vary: Origin
Vary ใช้ในการบอก Reverse Proxy (Cache, CDN) ว่า ถ้าจะ cache response นี้ ให้ cache แยกตาม Origin เช่นเราอนุญาตให้
example.comเข้าได้ แต่ไม่ให้evil.comเข้า แต่ถ้าเราไม่บอก CDN ว่าถ้าจะ cache ให้ cache แยกตาม Origin ตอนevil.comเข้ามา อาจจะได้ผลลัพท์ของexample.comกลับไป
ถ้าเราไม่อยากให้ Browser ส่ง request เข้ามาหล่ะ ?
ก็แค่ตอบ error อะไรกลับไปก็ได้ เช่น
HTTP/1.1 403 Forbidden
Content-Type: text/plain
Content-Length: 9
Forbidden
นอกจากนี้เมื่อ Browser ส่ง Request จริงเข้ามา เราจะยังต้องตอบ browser บางคำตอบซ้ำ
เช่น หลังจากที่ Browser ได้คำตอบว่า api.example.com อนุญาตให้ https://example.com ส่ง request เข้ามาได้
Browser จะส่ง Request ที่เราเขียนใน JavaScript เข้ามาว่า
POST /api1 HTTP/1.1
Host: api.example.com
Origin: https://example.com
Cookie: sess=1234
Content-Length: 16
Content-Type: application/json
X-Api-Key: key-1
{"text":"hello"}
เราจะต้องแนบ header กลับไปบอก browser ต่อว่า
ให้ https://example.com อ่านนะ เผื่อ response ของอีก Origin ถูก cache ไว้ browser จะไม่ส่ง response นี้ไปให้ JavaScript ถึงแม้ว่าเรา inspect ดูแล้วจะได้ success ก็ตาม
ให้ส่ง Cookie เข้ามาได้ (ถึงแม้ว่า Server จะประมวลผลทุกอย่างเสร็จแล้ว แต่ถ้า Browser ส่ง Cookie ไป แล้วไม่เห็น header นี้ใน response อีกรอบ ก็จะไม่ส่ง response ไปให้ JavaScript)
ผลลัพท์นี้ ให้ JavaScript อ่าน Header X-Request-Id ได้
ถ้าเราไม่บอก Browser ถึงแม้เราจะส่ง header นี้กลับไปใน response แต่เราจะไม่สามารถเขียน JavaScript เข้าไปอ่าน header ตัวนี้ได้
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Request-Id
X-Request-Id: req-123
Vary: Origin
Content-Type: application/json
Content-Length: 11
{"ok":true}
บางครั้งเราต้องการให้ทุก Origin เข้ามาได้ เราสามารถใส่
Access-Control-Allow-Origin: *
แต่เราจะ ไม่ สามารถทำแบบนี้ได้
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
เพราะถ้าเรา login ที่เว็บ https://bank.com แล้วทุกเว็บจะสามารถส่ง request มาโอนเงินออกจากบัญชีเราได้หมดเลย เพราะทุกเว็บที่ส่ง request มาที่ api ของ bank.com browser จะแนบ Cookie ที่ได้ตอน login ที่เว็บ bank.com มาด้วย
บางคนอยากให้ทุกเว็บส่ง Cookie เข้ามาได้ ก็ต้องลักไก่ เอา Origin ที่มาจาก request ใส่เข้าไปใน Allow Origin แทน แต่ต้องคิดดี ๆ ก่อนทำ ไม่ฉะนั้นทุกเว็บจะได้ Cookie ไปใช้ได้เลย
CORS คือสิ่งที่ Browser คุยกับ Server เพื่อถามว่า