Copenhagen Book: แนวทางการนำระบบยืนยันตัวตนสำหรับเว็บแอปพลิเคชันไปใช้
(thecopenhagenbook.com)- เป็นแนวทางพื้นฐานที่ใช้อ้างอิงได้เมื่อต้องออกแบบ การนำระบบยืนยันตัวตนไปใช้ ในเว็บแอปพลิเคชัน โดยมีเป้าหมายเพื่อเติมเต็มช่องว่างของเอกสารด้านการยืนยันตัวตนที่มีอยู่บนออนไลน์
- เป็นทรัพยากรแบบ ฟรี·โอเพนซอร์ส และให้บริการในรูปแบบที่ชุมชนร่วมกันดูแลรักษา
- เนื้อหาบางส่วนอาจมี มุมมองเชิงความเห็น ปะปนอยู่หรืออาจยังไม่สมบูรณ์ จึงเหมาะที่จะใช้เป็นเอกสารประกอบมากกว่าจะใช้เป็นเกณฑ์เดียว
- แนะนำให้ใช้งานควบคู่กับ OWASP Cheat Sheet Series ซึ่งเป็นเอกสารอ้างอิงด้านความปลอดภัยของการยืนยันตัวตน
- หากมีข้อเสนอแนะหรือข้อกังวล สามารถเปิด issue ใหม่เพื่อส่งต่อได้ และมีส่วนร่วมในการปรับปรุงเอกสารได้
วัตถุประสงค์และลักษณะของเอกสาร
- The Copenhagen Book ให้แนวทางทั่วไปที่ใช้อ้างอิงได้เมื่อต้องนำ การยืนยันตัวตน (auth) ไปใช้ในเว็บแอปพลิเคชัน
- รูปแบบการดำเนินงานมีดังนี้
- ให้ใช้งานฟรี
- เป็นโอเพนซอร์ส
- ชุมชนเป็นผู้ดูแลรักษา
- เนื้อหาอาจมีมุมมองเชิงความเห็นปะปนอยู่บ้างหรืออาจยังไม่สมบูรณ์ในบางครั้ง
เอกสารที่ควรดูควบคู่และวิธีมีส่วนร่วม
- เมื่อนำระบบยืนยันตัวตนไปใช้ แนะนำให้อ้างอิงร่วมกับ OWASP Cheat Sheet Series
- หากมีข้อเสนอแนะหรือข้อกังวล สามารถเปิด issue ใหม่ได้
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ถ้าผมไม่ได้เข้าใจผิด บทความนี้น่าจะเขียนโดยผู้สร้าง Lucia ไลบรารี authentication ยอดนิยมสำหรับ TypeScript
ล่าสุดเขาประกาศว่าไลบรารี Lucia ไม่ใช่วิธีที่ ergonomic สำหรับการทำ authentication อีกต่อไป จึงจะเลิกพัฒนาไลบรารีและแทนที่ด้วยชุดไกด์แบบบทความ
พรีวิวไกด์ช่วงแรกอ่านง่าย และเข้ากับ The Copenhagen Book ได้ดี
https://github.com/lucia-auth/lucia
https://github.com/lucia-auth/lucia/discussions/1707
https://lucia-next.pages.dev/
เป็นเรื่องที่หาได้ยากที่เขาเห็นสัญญาณว่าความซับซ้อนกำลังจะระเบิด ยอมรับว่าไลบรารีไม่เป็นประโยชน์กับตัวเองอีกต่อไป แล้วถอยออกมาอย่างถ่อมตัวจากเส้นทางมาตรฐานของ library bloat ที่พบบ่อย
จริง ๆ แล้ว 99% ของคนต้องการแค่ login/logout และสิ่งนี้มีประโยชน์มากเมื่อมาในรูปไลบรารี
ถ้าต้องให้ passkey ของ Web 8.0 ผ่านซ็อกเก็ต elliptic curve บน WASM ก็ทำเองหรือใช้ Auth0 ได้ แต่การโยนสเปก OAuth กับรายการกับดักให้คนที่ทำแอป CRUD สำหรับสูตรอาหาร แล้วบอกให้สร้างระบบ authentication เอง มันแปลก
คนคนนั้นควรได้โฟกัสกับไอเดียจริง ๆ มากกว่าการประดิษฐ์งานท่อใหม่ และหลายคนอาจลงเอยด้วยการทำผิดจนแทบเท่ากับไม่มี authentication เลย
ส่วนนี้ผิด: “ที่อยู่อีเมลไม่แยกแยะตัวพิมพ์ใหญ่-เล็ก”
https://thecopenhagenbook.com/email-verification
ตามมาตรฐานอีเมล ที่อยู่อีเมลแยกแยะตัวพิมพ์ใหญ่-เล็ก
ถ้าเปลี่ยนอีเมลเป็นตัวพิมพ์เล็กระหว่างขั้นตอนส่ง อีเมลอาจถูกส่งไปผิดคนได้ และเป็นเรื่องอันตรายใน authentication
ผู้ให้บริการอีเมลชื่อดังจำนวนมากเลือกไม่แยกแยะตัวพิมพ์ใหญ่-เล็ก แต่ถ้าเป็นไซต์ที่จัดการ authentication ทั่วไป ก็ควรแนะนำกรณีทั่วไป
https://stackoverflow.com/questions/9807909/are-email-addres...
เพิ่มเติมคือไม่ได้ชัดเจนเสมอไปว่าตัวพิมพ์อีกแบบของอักขระหนึ่งคืออะไร และอาจเปลี่ยนไปตามเวลาได้ด้วย เช่น ตัวพิมพ์ใหญ่ ß ในภาษาเยอรมันเพิ่งถูกเพิ่มเข้า Unicode ในปี 2008 ดังนั้นในการเขียนโปรแกรมทั่วไป ถ้าเป็นไปได้ควรหลีกเลี่ยงการแยกแยะตัวพิมพ์ใหญ่-เล็ก
มาตรฐานพูดอย่างหนึ่ง แต่ implementation ทำงานอีกอย่าง และในกรณีนี้ถ้าทำตามข้อความในมาตรฐานอย่างเดียวจะเกิดปัญหาในโลกจริง
แบบนี้อีเมลจะถูกส่งไปยังที่อยู่ตามตัวพิมพ์ใหญ่-เล็กที่กรอกครั้งแรก และการล็อกอินก็ทำได้โดยไม่สนตัวพิมพ์ใหญ่-เล็ก
ข้อเสียคือไม่สามารถมีผู้ใช้สองคนที่มีอีเมลต่างกันเฉพาะตัวพิมพ์ใหญ่-เล็กได้ แต่ผมคิดว่าระดับนั้นยอมรับได้
อีเมลไม่แยกแยะตัวพิมพ์ใหญ่-เล็ก แต่ username ที่สร้างจากอีเมลแยกแยะตัวพิมพ์ใหญ่-เล็ก ดังนั้นถ้าสร้างบัญชีด้วยอีเมลที่มีตัวพิมพ์ใหญ่ ก็ต้องพิมพ์ตัวพิมพ์ใหญ่-เล็กให้ตรงแบบนั้นจึงจะล็อกอินได้ และล็อกอินด้วยอีเมลแบบไม่สนตัวพิมพ์ใหญ่-เล็กไม่ได้
หลังจากนั้นถ้ากำจัดความแตกต่างด้านภาษาและวัฒนธรรมได้ด้วย โค้ดหลายพันล้านบรรทัดจะหายไป ผมได้ยินเสียง repository หดเล็กลงแล้ว
เช่น รายการผู้ใช้หนึ่งมี JohnDoe@example.com อีกรายการมี johndoe@example.com ทั้งที่เห็นชัดว่าเป็นคนเดียวกัน
แถมค่าพวกนี้มาจากคนละระบบกันด้วยเลยยิ่งปวดหัว
ผมคิดว่าเมทริกซ์ความเสี่ยงของอีเมลที่ไม่แยกแยะตัวพิมพ์ใหญ่-เล็กดีกว่าอีเมลที่แยกแยะตัวพิมพ์ใหญ่-เล็กมาก กล่าวคือควรเปลี่ยนอีเมลทั้งหมดเป็นตัวพิมพ์เล็ก และ The Copenhagen Book ก็ถูกในประเด็นนี้
ดีมาก ผมหงุดหงิดมาตลอดที่เอกสารด้านความปลอดภัย 90% ดูเหมือนถูกทำให้คนที่ไม่ใช่ผู้เชี่ยวชาญด้านความปลอดภัยแทบไม่มีทางเข้าใจได้ โดยเฉพาะ เอกสาร cryptography
แต่แทบทุกหน้าที่นี่ชัดเจน กระชับ ตรงประเด็น และนำไปใช้ได้ทันที เลยถูกใจ
แต่หน้า elliptic curve ยังเข้าใจยากพอ ๆ กับเอกสาร cryptography อื่น ๆ
หลักการทำงานของมันเป็นคณิตศาสตร์ จึงมองว่ามันทึบโดยเนื้อแท้
พอเข้าใจ finite field, elliptic curve และโดยเนื้อแท้แล้วคือกรุ๊ปของจำนวนเต็ม ก็ทำให้เข้าใจ cryptography ได้มากขึ้น และส่วนใหญ่ก็เป็นรูปแบบหนึ่งของ ปัญหา discrete logarithm ไม่ทางใดก็ทางหนึ่ง
ถ้ามีเอกสารทางเลือกในหัวข้อคล้าย ๆ กันก็น่าจะดี เช่น เอกสารแนว OWASP Cheatsheet แต่เป็นมุมมองที่ใช้งานได้จริงมากกว่า
ผมเคารพนะ แต่ค่อนข้างกังขากับเอกสารนี้อยู่บ้าง
ชื่อฟังดูยิ่งใหญ่มาก การเรียกเอกสารให้เหมือนกับว่านักวิจัยจากมหาวิทยาลัยใน Copenhagen เป็นคนเขียน ถือเป็นกลเม็ดทางการตลาดที่ยอดเยี่ยม
จริงอยู่ที่ Lucia เป็นไลบรารีที่ค่อนข้างได้รับความนิยม แต่ไม่ได้หมายความว่ามันกำลังส่งเสริมแนวปฏิบัติที่ดีที่สุด หรือควรมองว่าผู้เขียนเป็นผู้มีอำนาจในสาขาสำคัญนี้
มีบางส่วนในการออกแบบของ Lucia ที่ผมไม่ชอบด้วย เช่น เสนอว่าเมื่อโทเค็นของผู้ใช้ใกล้หมดอายุ ให้ยืดอายุโทเค็นเดิมแทนที่จะสร้างโทเค็นความปลอดภัยใหม่
ดูเหมือนเป็นพฤติกรรมที่ไม่ปลอดภัยมาก เพราะโทเค็นแทบจะมีชีวิตอยู่ตลอดไปและสามารถถูกนำไปใช้ในทางที่ผิดได้เรื่อย ๆ ซึ่งขัดกับแนวปฏิบัติด้านความปลอดภัยที่ดีเรื่องการจำกัดอายุของโทเค็น
ทั้ง Lucia และ “Copenhagen Book” แนะนำวิธีนี้: https://thecopenhagenbook.com/sessions#session-lifetime
นี่เป็นวิธีที่พบได้ทั่วไป และมักเรียกว่า rolling session
ตัวโทเค็นเองควรเป็น immutable ตั้งแต่แรก จึงไม่ควรเปลี่ยนอายุของมันได้ ดังนั้นการต่ออายุโทเค็นจึงไม่ใช่การแก้ไขโทเค็นเดิม แต่เป็นการให้โทเค็นใหม่
กรณีแรก ผู้โจมตีขโมยโทเค็นไปแล้วใช้ได้ตลอดไป กรณีที่สอง ผู้โจมตีก็ขโมยโทเค็นไป แล้วรับโทเค็นใหม่ก่อนหมดอายุเล็กน้อย ก็ยังแอบอ้างเป็นผู้ใช้ต่อไปได้
ถ้าผู้ใช้ถูกเตะออก อาจจะสังเกตเห็นอะไรบางอย่าง แต่โอกาสน้อย และเพื่อประสบการณ์ผู้ใช้ที่ดี อย่างไรก็ต้องมีช่วงผ่อนผันอยู่ดี ไม่อย่างนั้นคำขอแบบขนานก็จะทำให้ผู้ใช้ปกติมีปัญหา
อาจใช้โทเค็นที่สอง หรือ refresh token ได้ แต่ก็แค่ย้ายความเสี่ยงไปไว้ที่โทเค็นนั้น ตอนนี้ต้องกังวลว่า refresh token จะถูกขโมยและถูกใช้ในทางที่ผิดได้ตลอดไป
refresh token มีประโยชน์ตรงที่ไม่ต้องแตะฐานข้อมูลทุกคำขอ โดยปกติโทเค็นเซสชันอายุสั้นสามารถตรวจสอบได้โดยไม่ต้องใช้ฐานข้อมูล เช่น JWT ที่มีลายเซ็น
แต่หากถูกขโมยจะเพิกถอนไม่ได้ และยังใช้ได้จนกว่าจะหมดอายุ ดังนั้นเพื่อลดความเสียหายจึงควรกำหนดเวลาหมดอายุให้สั้น
ในทางกลับกัน refresh token จะตรวจสอบกับฐานข้อมูล โทเค็นตัวที่สองไม่ได้เพิ่มความปลอดภัยด้วยตัวเอง แต่การค้นฐานข้อมูลต่างหากที่เพิ่มความปลอดภัย เพราะสามารถลบออกจากฐานข้อมูลเพื่อเพิกถอนได้
อย่างน้อยตามตัวอย่าง Lucia จะค้นฐานข้อมูลเสมอ จึงสามารถเพิกถอนโทเค็นได้ทุกเมื่อ
หากต้องการลดความเสี่ยง ควรให้ผู้ใช้ดูและยุติเซสชันที่กำลังใช้งานอยู่ได้ ถ้าเป็นไปได้ควรแสดงเวลา ตำแหน่ง และข้อมูลอุปกรณ์ด้วย เช่น “เข้าสู่ระบบจาก iPhone ใน Copenhagen เมื่อวานเวลา 00:00 น.”
อาจแจ้งผู้ใช้เมื่อมีการเข้าสู่ระบบจากตำแหน่งหรืออุปกรณ์ใหม่ด้วย
สุดท้ายแล้ว ไม่มีวิธีใดที่จะทำให้เซสชันที่คงอยู่ยาวนานปลอดภัยอย่างสมบูรณ์ได้
ผมคิดว่าเป็นประเด็นที่ดี แต่คงเรียกตัวเองว่าเป็นผู้เชี่ยวชาญไม่ได้
ใน OAuth มีสองสิ่งที่ทุกคนมองข้าม และไม่ค่อยเห็นชัดนัก
ดีใจที่มีคนชี้ให้เห็นหนึ่งในนั้น เวลาใช้โทเค็น ต้องใช้ธุรกรรมเป็นกลไก distributed lock เสมอ
สำหรับ refresh token ความสำคัญนี้เพิ่มเป็นสองเท่า สี่เท่า หากใช้โทเค็นเดียวกันมากกว่าหนึ่งครั้ง ผู้ใช้นั้นจะถูกล็อกเอาต์
ไม่สำคัญว่าระบบจะรันอยู่บนเครื่องเดียวหรือ N เครื่อง หากมีคำขอที่แนบ refresh token มากกว่าหนึ่งคำขอกำลังดำเนินพร้อมกัน ซึ่งเกิดขึ้นบ่อยมาก ก็จะทำให้ผู้ใช้ถูกล็อกเอาต์ และมักจบด้วย 500
refresh token เป็นแบบใช้ครั้งเดียว
อีกสิ่งที่นักพัฒนาและเฟรมเวิร์กการยืนยันตัวตนมักพลาดคือพารามิเตอร์ “state”
เมื่อดูทิศทางที่การยืนยันตัวตนยุคนี้กำลังพัฒนาโดยรวม มันดูไม่ใช่ security through obscurity แต่ใกล้เคียงกับ ความไม่เสถียรจากความคลุมเครือ มากกว่า
ถ้าสถานะของแอปพลิเคชันเข้ามาเกี่ยวด้วย ก็ต้องปรับตรรกะของแอปพลิเคชันให้เข้ากับการยืนยันตัวตน และแอปพลิเคชันก็ต้องตรวจสอบว่ามีใครขโมย refresh token ไปหรือไม่
ใช้ธุรกรรมอย่างเดียวแก้ไม่ได้ เช่น ถ้าเปิดสองแท็บอย่างรวดเร็ว ก็จะยิงเซิร์ฟเวอร์ด้วย refresh token เดิมสองครั้ง
เอกสารนั้นชอบแนวทาง refresh token rotation แต่ก็กล่าวถึงความยากที่เห็นได้ชัดในสภาพแวดล้อมแบบคลัสเตอร์ด้วย: https://datatracker.ietf.org/doc/html/rfc6819#section-5.2.2....
คำขอหลายรายการเกิดขึ้นพร้อมกันเสมอ เช่น เบราว์เซอร์เริ่มขึ้นพร้อมหลายแท็บ หรือแอปสมาร์ตโฟนเริ่มทำงานแล้วส่งคำขอหลายรายการพร้อมกันเป็นเรื่องปกติ
อยากให้เว็บไซต์มากขึ้นมีตัวเลือกแบบ “ทำให้เซสชันไม่หมดอายุจนกว่าจะออกจากระบบเอง ฉันเข้าใจความเสี่ยง”
ทุกวันนี้ปุ่ม “remember me” แทบไม่มีผลอะไรเลย
วันทั้งวันถูกตัดจังหวะตลอดเพราะเซสชันหมดอายุ น่าหงุดหงิดมาก โดยเฉพาะ GitHub ที่ไม่ชอบเป็นพิเศษ เพราะใช้ประมาณสัปดาห์ละครั้ง เซสชันจึงหมดอายุตลอด แถมยังบังคับ 2FA ทำให้ต้องหยิบมือถือขึ้นมากรอกตัวเลขทุกครั้ง
ประสบการณ์ผู้ใช้ก็แย่มากอยู่แล้ว แต่ถึงจะไม่มีหลักฐาน ผมคิดว่าการบังคับให้ล็อกอินซ้ำๆ แบบนี้ทำให้ผู้ใช้เหนื่อยล้าและระวังตัวน้อยลง
ถ้าต้องล็อกอินเข้าเว็บหนึ่งหลายครั้งต่อสัปดาห์ พอถึงครั้งที่ 60 ก็มีโอกาสที่เว็บฟิชชิงจะแทรกเข้ามาได้ง่ายขึ้น ในทางกลับกัน ถ้าบัญชีที่แทบไม่เคยต้องล็อกอินอยู่ๆ มาขอรหัสผ่าน ก็จะรู้สึกผิดปกติและระวังตัวมากขึ้น
สุดท้ายบริษัทต่างๆ ควรเรียนรู้ว่าคนแต่ละคนมี ระดับการยอมรับความเสี่ยง และท่าทีด้านความปลอดภัยต่างกัน แล้วควรมีตัวเลือกให้
เพิ่มเติมคือ เซสชันของ GitHub หมดอายุบ่อยและ 2FA น่ารำคาญมากจนผมย้ายไป Gitea แล้วปิดการหมดอายุไปเลย เหตุผลที่ย้าย 90% คือเรื่องนี้
เพราะเข้าถึงได้เฉพาะจากเครือข่ายของผมเอง เลยรู้สึกว่าความปลอดภัยกลับดีขึ้นด้วยซ้ำ โมเดลความปลอดภัยที่ไม่ยืดหยุ่นทำให้บริษัทเสียลูกค้าได้จริงๆ
โดยคร่าวๆ คือถ้ามีการใช้งานภายใน 30 วัน ก็ให้เซสชันต่ออายุไปอีก 30 วัน
https://thecopenhagenbook.com/sessions#session-lifetime
ที่แย่กว่านั้นคือเซสชันดูเหมือนจะอยู่ได้นานกว่าหนึ่งสัปดาห์ประมาณ 1–2 นาที ทำให้สัปดาห์นี้ผมถูกเด้งออกทันทีหลังเริ่มทำงานต่อจากสัปดาห์ก่อน
หลายสัปดาห์ก่อน ผมล็อกอินเพื่อจะจดโน้ตก่อนประชุมประจำ แล้วก็โดนบังคับล็อกอินกลางการประชุมนั้นติดต่อกันหลายสัปดาห์
มีตั้งค่าหลายอย่างมากที่ทำให้ปุ่ม “remember me” แบบนั้นใช้ไม่ได้
ส่วนตัวผมไม่เคยมีปัญหากับการคงสถานะล็อกอินของเว็บไซต์ รวมถึง GitHub ด้วย
ช่วงหลังเพิ่งรู้จัก โปรโตคอล SRP และแปลกใจที่มันไม่ได้ถูกใช้หรือพูดถึงให้แพร่หลายกว่านี้
เป็นโปรโตคอลที่ค่อนข้างเรียบง่าย สามารถทำ zero-knowledge proof และสร้าง session token ระหว่างเซิร์ฟเวอร์กับไคลเอนต์ได้ในคราวเดียว
https://en.wikipedia.org/wiki/Secure_Remote_Password_protoco...
ชอบเอกสารนี้ คำแนะนำด้านความปลอดภัยจำนวนมากเข้าใจยาก และบางครั้งก็รู้สึกไร้เหตุผล เหมือนทนายที่แนะนำว่าอย่าทำอะไรเลย
ไกด์นี้ดีตรงที่กระชับอย่างสดชื่น ทำตามง่าย เข้าใจง่าย และให้คำแนะนำที่ตรงไปตรงมา
จะคอยอ่านคอมเมนต์ต่อว่ามีความเห็นแย้งหรือข้อควรระวังอะไรไหม แต่คงเอาไปตรวจเทียบกับโปรเจ็กต์ authentication ของผม
สิ่งหนึ่งที่อยากได้คือมี ส่วน JWT ถ้าความเห็นของผู้เขียนคือ “อย่าใช้” แค่พูดแบบนั้นก็ยังดี
หนึ่งในสิ่งที่ผมมักวิจารณ์ทีมความปลอดภัยคือ พวกเขาใช้เวลามากไปกับการบอกว่าอะไรไม่ควรทำ แทนที่จะเสนอเครื่องมือหรือวิธีการล่วงหน้าให้ผู้คนทำสิ่งที่อยากทำได้อย่างมีประสิทธิภาพ
เพราะอีเมลถือได้ว่าเป็นระบบที่ปลอดภัยกว่า และอาจไม่ได้เข้าถึงได้ทันทีหรือจากทุกที่
สงสัยว่า “auth” ในที่นี้หมายถึง authentication (authn) หรือ authorization (authz)
ดูเหมือนจะหมายถึง authentication แต่ถ้าชี้ให้ชัดเจนก็น่าจะดี
ขอแทรกบ่นสั้นๆ ที่เกี่ยวข้องนิดหน่อยว่า เบราว์เซอร์ในแอป กำลังทำลายเว็บ
เบราว์เซอร์ในแอปทำให้การใช้ social OAuth เป็นไปไม่ได้ บางกรณีเป็นไปไม่ได้จริงๆ ตามตัวอักษร และบางกรณีก็เป็นไปไม่ได้ในทางปฏิบัติ
ถ้ากดลิงก์ใน Instagram โดยปกติจะเปิดในเบราว์เซอร์ของ Instagram และถ้าลิงก์นั้นมี “Sign in with Google” อยู่ Google จะบล็อก “เบราว์เซอร์ที่ไม่ปลอดภัย” อย่าง Instagram จึงใช้งานไม่ได้
แม้แต่ “Sign in with Facebook” ก็ยังมีปัญหา ทั้งที่ Meta เป็นเจ้าของทั้ง Instagram และ Facebook เบราว์เซอร์ในแอปของ Facebook ก็มีปัญหาคล้ายกัน
กรณีที่กดลิงก์จากที่อย่าง Slack แล้วตอบข้อความ จากนั้นกลับไปที่เบราว์เซอร์คาดว่าหน้านั้นยังอยู่ แต่ Slack กลับกลืนมันไว้ในเบราว์เซอร์ของตัวเองจนหาไม่เจอที่ไหนเลย แทบจะไร้ประโยชน์เสมอ
โชคดีที่เมื่อกี้ลองตรวจดูแล้ว Slack มีวิธีปิดเบราว์เซอร์ในแอปได้