blog

สรุป CORS แบบสั้น ๆ

CORS ย่อมาจาก Cross-Origin Resource Sharing

Origin คือ header ตัวนึง ที่บอกว่า request ถูกเรียกจากที่ไหน จะต่างกับ Referer โดยที่ Origin จะไม่มี path มาด้วย

Origin ประกอบด้วย 3 ส่วน คือ

  1. Scheme เช่น http, https
  2. Hostname เช่น example.com, www.example.com
  3. Port เช่น 3000, 8000, 8080, 9000

ถ้าเข้าเว็บจาก file:// Origin จะเป็น null

Origin: null
Origin: <scheme>://<hostname>[:<port>]

Origin ที่ต่างกัน

เมื่อเกิด CORS

เมื่อเราต้องการ 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 ก่อนว่า

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 ว่า

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 ต่อว่า

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}

สำคัญมาก ๆ กับการ Allow all origin

บางครั้งเราต้องการให้ทุก 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 เพื่อถามว่า

References