Photo by Mohammad Rahmani on Unsplash
ตั้งแต่เวอร์ชัน 84 เป็นต้นมา Chrome ได้กำหนดค่าเริ่มต้นของแอตทริบิวต์ SameSite ของคุกกี้เป็น Lax บริการที่ใช้ Third-party cookies อาจได้รับผลกระทบหากไม่ได้ตั้งค่า SameSite ไว้อย่างเหมาะสม
ภาพรวม
คุกกี้ (Cookies) เป็นกลไกที่ใช้ในบริการเว็บเพื่อเก็บสถานะ มักใช้ในการรักษาการเข้าสู่ระบบ ตะกร้าสินค้า การติดตามโฆษณา ฯลฯ อย่างไรก็ตาม การใช้งานคุกกี้อย่างแพร่หลายก็นำมาซึ่งความกังวลด้านความเป็นส่วนตัวและความปลอดภัย และ SameSite ก็เกิดขึ้นมาเพื่อแก้ไขปัญหาเหล่านี้
First-Party and Third-Party
ขึ้นอยู่กับ แหล่งที่มาของคุกกี้ (Set-Cookie) คุกกี้แต่ละตัวจะมีโดเมน (Domain) เฉพาะ เมื่อดูจาก URL ปัจจุบันในเบราว์เซอร์ของผู้ใช้ หากโดเมนของคุกกี้ตรงกับ URL ปัจจุบัน จะถือว่าเป็น First-Party แต่ถ้าไม่ตรง จะถือว่าเป็น Third-Party
Third-party
ตัวอย่างเช่น เมื่อเรียกดูเว็บไซต์ a.com มีการส่งคำขอ (Request) ไปยัง third-party.com และได้รับคุกกี้จาก third-party.com เนื่องจากเบราว์เซอร์จะแนบคุกกี้ที่มีโดเมนเดียวกันไปกับคำขอโดยอัตโนมัติ หากหลังจากนั้นคุณเรียกดูเว็บไซต์อื่น เช่น b.com และมีการส่งคำขอไปยัง third-party.com เซิร์ฟเวอร์ก็จะได้รับคุกกี้ สำหรับทั้งสองเว็บไซต์นี้ คุกกี้ของ third-party.com คือ Third-party
First-party
หากคุณเรียกดูเว็บไซต์ที่ตรงกับโดเมน third-party.com คุกกี้ก็จะถูกส่งไปด้วย ในกรณีนี้ คุกกี้นี้จะเรียกว่า First-party
Same-Origin and Same-Site
ตัวอย่างก่อนหน้านี้กล่าวถึงการใช้โดเมนเพื่อกำหนดประเภทของคุกกี้ แต่วิธีที่ดีกว่าคือการดูว่า Site เหมือนกันหรือไม่ สิ่งนี้เกี่ยวข้องกับ Same-origin ที่เห็นกันบ่อยๆ หรือไม่?

Origin
Origin ประกอบด้วย Scheme, Host และ Port วิธีการตัดสินนั้นง่ายมาก: หาก Scheme, Host และ Port ของ URL สองแห่งเหมือนกันทั้งหมด จะถือว่าเป็น Same-origin หากไม่เหมือนกัน จะถือว่าเป็น Cross-origin
Site
การตัดสิน Same-Site เกี่ยวข้องกับ Effective top-level domains (eTLDs) eTLD ทั้งหมดถูกกำหนดไว้ใน Public Suffix List และ Site ประกอบด้วย eTLD บวกกับคำนำหน้า (prefix)
ตัวอย่างเช่น:
github.io มีอยู่ใน Public Suffix List เมื่อเพิ่มคำนำหน้า (เช่น a.github.io) จะกลายเป็น Site ดังนั้น a.github.io และ b.github.io จึงเป็น Site สองแห่งที่แตกต่างกัน (Cross-site)
example.com ไม่มีอยู่ใน Public Suffix List แต่มี .com ดังนั้น example.com จึงเป็น Site และ a.example.com กับ b.example.com เป็น Site เดียวกัน (Same-site)
โปรดทราบว่า Site ไม่รวม Port ดังนั้นแม้ว่า Port จะต่างกัน ก็ยังสามารถเป็น Same-site ได้
Why SameSite?
กลไกที่ “ทุกคำขอจะแนบคุกกี้ของโดเมนนั้นไปด้วย” ก่อให้เกิดปัญหาด้านความปลอดภัยและปัญหาอื่นๆ ที่สำคัญที่สุดคือ Cross-site request forgery (CSRF)
CSRF
สมมติว่าผู้ใช้เคยเข้าสู่ระบบ example.com และได้รับคุกกี้ เมื่อผู้ใช้เรียกดูเว็บไซต์อันตราย evil.com JavaScript ในเว็บไซต์นั้นสามารถส่ง POST Request ไปยัง example.com/pay?amount=1000 เบราว์เซอร์จะแนบคุกกี้ของ example.com ไปโดยอัตโนมัติ และผู้ใช้ก็จ่ายเงิน 1000 ดอลลาร์โดยไม่รู้ตัว เซิร์ฟเวอร์ไม่สามารถระบุได้ว่าคำขอนี้มาจากที่ใด
ข้อจำกัด
คุกกี้เองไม่สามารถตั้งค่าให้ส่งเฉพาะในสภาพแวดล้อม First-party ได้ ดังนั้นคำขอก็จะแนบคุกกี้ไปในทุกสภาพแวดล้อม เซิร์ฟเวอร์ไม่สามารถระบุแหล่งที่มาของคำขอได้และทำได้เพียงตอบกลับตามปกติ ในขณะเดียวกัน Client ก็สิ้นเปลืองแบนด์วิดท์ในการส่งคุกกี้ที่ไม่มีประโยชน์
ทางแก้
ด้วยแอตทริบิวต์ SameSite เราสามารถกำหนดเงื่อนไขในการส่งคุกกี้ในสภาพแวดล้อมต่างๆ ได้อย่างอิสระ
SameSite
แอตทริบิวต์ SameSite มีค่าสามค่า การตั้งค่าเป็น Strict หรือ Lax สามารถจำกัดให้ส่งคุกกี้เฉพาะในคำขอแบบ Same-Site เท่านั้น หากเว้นว่างไว้ พฤติกรรมจะขึ้นอยู่กับเบราว์เซอร์ สำหรับ Chrome ค่าเริ่มต้นคือ Lax
Strict
คุกกี้จะถูกส่งเฉพาะในสภาพแวดล้อม First-party เท่านั้น แต่มีปัญหาคือ สมมติว่าผู้ใช้เห็นลิงก์โพสต์ Facebook บน example.com (สมมติว่าเป็น fb.com) แม้ว่าผู้ใช้จะเคยเข้าสู่ระบบ fb.com และมีคุกกี้อยู่ แต่เมื่อคลิกลิงก์ คุกกี้จะไม่ถูกส่งเนื่องจากทั้งสองเว็บไซต์เป็น Cross-site ดังนั้นพวกเขาจะเห็นเพียงหน้าเข้าสู่ระบบเท่านั้น
ดังนั้น Strict จึงเหมาะสำหรับการดำเนินการที่ละเอียดอ่อน เช่น การลบโพสต์ การชำระเงิน ฯลฯ
Lax
เพื่อแก้ปัญหาข้อจำกัดที่เข้มงวดเกินไปของ Strict Lax อนุญาตให้ส่งคุกกี้ได้แม้ในสถานการณ์ Cross-site ในกรณีต่อไปนี้:
- การพิมพ์ URL ในแถบที่อยู่
- การคลิกลิงก์
<a href="..."> - การส่งแบบฟอร์ม
<form method="GET"> - การแสดงผลล่วงหน้า (Prerendering)
<link rel="prerender" href="...">
กรณีเหล่านี้มีจุดร่วมสองประการ: ทั้งหมดเป็นคำขอแบบ GET และทั้งหมดกระตุ้นให้เกิด Top-level Navigation ซึ่งช่วยหลีกเลี่ยงปัญหาที่ Strict บังคับให้ต้อง เข้าสู่ระบบใหม่ ทุกครั้ง และยังป้องกันการ ส่งคุกกี้โดยไม่รู้ตัวเมื่อเรียกดูเว็บไซต์อื่น อีกด้วย
Lax + POST
อย่างไรก็ตาม เพื่อหลีกเลี่ยงการทำลายขั้นตอนการเข้าสู่ระบบที่มีอยู่บางส่วน ปัจจุบัน Chrome ได้ผ่อนคลายข้อจำกัดเล็กน้อยสำหรับ SameSite=Lax เพื่อให้เวลานักพัฒนาในการปรับตัว
ภายในสองนาทีหลังจากตั้งค่าคุกกี้ ไม่ว่า Request Method จะเป็นอะไร ตราบใดที่มันกระตุ้นให้เกิดการนำทางของหน้าเพจระดับบนสุด (Top-level page navigation) คุกกี้ก็จะถูกส่งไป ซึ่งหมายความว่าหากเบราว์เซอร์เปลี่ยนหน้า เช่น การส่งแบบฟอร์ม <form method="POST">
สำหรับรายละเอียด โปรดดู กระทู้ เกี่ยวกับ Lax + POST
None
ในการส่ง Third-party cookie คุณต้องตั้งค่า SameSite=None; Secure ใช่แล้ว จากนี้ไป หากคุณต้องการส่ง Third-party cookie ในสภาพแวดล้อมการทดสอบ โปรดเตรียม https://localhost ไว้ด้วย
นอกจากนี้ การส่ง Cross-Origin Request ผ่าน XHR/Fetch จำเป็นต้องตั้งค่า withCredentials: true เพื่อส่งคุกกี้และทำให้ Set-Cookie ใน Response header มีผล เซิร์ฟเวอร์ต้องตั้งค่า Access-Control-Allow-Credentials: true ใน Response header ด้วย เพื่อให้ JavaScript สามารถเข้าถึงเนื้อหา Response ได้
เบราว์เซอร์ที่ไม่รองรับ
ไม่ใช่ทุกเบราว์เซอร์ที่รองรับกฎ SameSite ล่าสุด ดังนั้นจึงสามารถเพิ่มวิธีแก้ปัญหาชั่วคราว (Workaround) บางอย่างบนเซิร์ฟเวอร์เพื่อรองรับเบราว์เซอร์หลายตัว:
ตั้งค่าคุกกี้ทั้งสองแบบ
วิธีนี้สามารถแก้ปัญหาได้สำหรับเกือบทุกเบราว์เซอร์ แต่ข้อเสียคือจะมีคุกกี้สองชุด:
Set-cookie: name=value; SameSite=None; Secure
Set-cookie: name-legacy=value; Secure
โค้ดฝั่งเซิร์ฟเวอร์:
if (req.cookies['name']) {
// ใช้ตัวใหม่ถ้ามี
cookieVal = req.cookies['name'];
} else if (req.cookies['name-legacy']) {
// มิฉะนั้นให้ใช้ตัวเก่า
cookieVal = req.cookies['name-legacy'];
}
User Agent
กำหนดเบราว์เซอร์โดยใช้ User agent ของ Request เพื่อตัดสินใจเนื้อหาของ Set-Cookie วิธีนี้ต้องแก้ไขโค้ดที่ตั้งค่าคุกกี้เท่านั้น โดยไม่ต้องเปลี่ยนส่วน Parsing อย่างไรก็ตาม วิธีการตัดสินนี้มีตัวแปรมากกว่าและทำให้ตั้งค่าคุกกี้ผิดได้ง่ายกว่า
ทบทวน
- คุกกี้ที่ไม่ได้ตั้งค่าแอตทริบิวต์ SameSite จะเป็น
SameSite=Laxตามค่าเริ่มต้น และไม่สามารถส่งได้ในสภาพแวดล้อม Cross-site - หากต้องการส่งคุกกี้ใน Cross-site คุณต้องตั้งค่า
SameSite=None; Secure - คุณสามารถใช้ SameSite sandbox เพื่อทดสอบว่าเบราว์เซอร์ปัจจุบันของคุณสอดคล้องกับกฎ SameSite ล่าสุดหรือไม่