ตรวจ ID Token ของ LINE Login ที่ฝั่งเซิร์ฟเวอร์ ก่อนจะเชื่อว่าเป็นคนนั้นจริง
สรุปสั้น ๆ
id_token ของ LINE Login คือ JWT ที่ลงลายเซ็นแล้ว แต่จะเชื่อได้ก็ต่อเมื่อเซิร์ฟเวอร์ของคุณตรวจเองว่าลายเซ็นถูก ผู้ออกคือ LINE ผู้รับคือ channel ของคุณ ยังไม่หมดอายุ และ nonce ตรงกับที่ส่งไปตอนเริ่มล็อกอินและใช้ได้ครั้งเดียว ห้ามรับ userId ที่เบราว์เซอร์ส่งมาตรง ๆ เพราะใครก็แก้ค่านั้นได้
สมมติหน้าเว็บของร้านมีปุ่มรับคูปอง เมื่อกด หน้าเว็บส่ง JSON ไปที่เซิร์ฟเวอร์ว่า ‘userId ของฉันคือ U123…’ แล้วเซิร์ฟเวอร์ผูกคูปองกับค่านั้น ฟังดูปกติ จนกระทั่งมีคนเปิดเครื่องมือในเบราว์เซอร์ แก้ userId เป็นของคนอื่น แล้วรับคูปองและผูกประวัติปลอมเข้ากับบัญชีนั้นได้
ปัญหาคือเซิร์ฟเวอร์เชื่อค่าที่มาจากฝั่งที่ไม่มีใครควบคุม วิธีแก้คือเปลี่ยนจากส่ง userId เป็นส่ง id_token แล้วให้เซิร์ฟเวอร์พิสูจน์ก่อนเชื่อ
id_token คืออะไร และต้องตรวจอะไรบ้าง
id_token เป็น JWT ที่ LINE ออกให้เมื่อขอสิทธิ์ openid ข้างในมี claim เช่น ผู้ออก ผู้รับ เวลาหมดอายุ และตัวระบุผู้ใช้ ค่าที่แน่นอนของแต่ละ claim และอัลกอริทึมการเซ็นขึ้นกับเวอร์ชันของ LINE Login ให้ใช้ค่าตามเอกสาร LINE Developers ล่าสุดเสมอ
ลำดับที่ผมใช้คือ ตรวจโครงสร้างและลายเซ็นก่อน แล้วค่อยอ่านค่าใน claim ถ้าอ่าน claim ก่อนแล้วตัดสินใจจากมัน เท่ากับเชื่อข้อมูลที่ยังไม่พิสูจน์ และมีโค้ดจำนวนไม่น้อยที่ถอดรหัส token เพื่อดูชื่อผู้ใช้โดยไม่ตรวจลายเซ็นเลย ทั้งที่ใครก็สร้าง token ปลอมที่ถอดรหัสออกมาหน้าตาเหมือนของจริงได้
| สิ่งที่ตรวจ | ป้องกันอะไร |
|---|---|
| ลายเซ็นถูกต้อง | ใครแก้เนื้อหา token |
| ผู้ออกคือ LINE | token ที่ออกจากแหล่งอื่น |
| ผู้รับคือ channel ของเรา | token ที่ออกให้แอปอื่นแล้วเอามาใช้กับเรา |
| ยังไม่หมดอายุ | token เก่าที่รั่วไปแล้ว |
| nonce ตรงและยังไม่เคยใช้ | การเล่นซ้ำของ token ที่ถูกขโมย |
ตรวจเอง หรือให้ LINE ตรวจให้
LINE มีบริการตรวจ id_token ให้ โดยส่ง token และ Channel ID ไปถาม ได้ผลเป็น claim ที่ผ่านการตรวจ ชื่อ endpoint และพารามิเตอร์ให้ดูจากเอกสารล่าสุด ผมไม่ยกชื่อมาเขียนซ้ำ เพราะจำผิดแล้วอันตรายกว่าไม่เขียน ข้อดีคือโค้ดสั้นและไม่ต้องดูแลกุญแจ ข้อเสียคือทุกการล็อกอินต้องพึ่งการเรียกออกไปอีกชั้น
อีกทางคือตรวจเองด้วยไลบรารี JWT ที่เชื่อถือได้ ใช้กุญแจสาธารณะของ LINE และแคชไว้ ต้องจัดการเรื่องกุญแจที่หมุนเวียนด้วย ทางนี้เร็วกว่าแต่รับภาระเองมากกว่า ทีมเล็กส่วนใหญ่ผมแนะนำเริ่มจากทางแรก แล้วค่อยย้ายเมื่อปริมาณล็อกอินสูงจนความหน่วงเป็นปัญหาจริง
nonce กับการเล่นซ้ำ
ค่า state ที่ใช้กันการโจมตีแบบ CSRF เป็นคนละตัวกับ nonce อย่าใช้แทนกัน แนวคิดเรื่องกันการยิงซ้ำของทั้งสองคล้ายกับที่ใช้ตรวจเว็บฮุคใน เรื่องตรวจลายเซ็นเว็บฮุคก่อนส่ง CAPI
- สร้างค่า nonce แบบสุ่มที่เดายาก ผูกกับเซสชันของเราและเก็บไว้ฝั่งเซิร์ฟเวอร์
- ส่ง nonce ไปตอนเริ่มล็อกอิน LINE จะใส่ค่าเดียวกันกลับมาใน id_token
- เมื่อได้ token ให้เทียบ nonce ใน token กับที่เก็บไว้ ถ้าไม่ตรงหรือไม่มีให้ปฏิเสธ
- ลบ nonce ทิ้งทันทีที่ใช้ เพื่อให้ใช้ได้ครั้งเดียว และกำหนดอายุสั้น
ตรวจผ่านแล้ว เก็บอะไรและห้ามเก็บอะไร
- ใช้ตัวระบุผู้ใช้จาก token ที่ตรวจผ่านแล้วเท่านั้น ห้ามหยิบจากฟิลด์ที่เบราว์เซอร์ส่งแยกมา
- ถ้าระบบมีหลายบริการที่ต้องรู้ตัวตนผู้ใช้ ให้ตรวจที่ประตูเดียว แล้วส่งต่อผลที่เชื่อถือได้ภายในองค์กร อย่าให้ทุกบริการรับ token แล้วตรวจกันเองคนละแบบ เพราะวันที่ต้องแก้กฎการตรวจจะแก้ไม่ครบทุกที่
- ห้ามบันทึก token ดิบลง log เพราะคนที่อ่าน log เอาไปเล่นซ้ำได้ถ้ายังไม่หมดอายุ
- เก็บชื่อและอีเมลเท่าที่ธุรกิจต้องใช้ ตามหลักใน เรื่องเก็บ LINE User ID ตาม PDPA
- เขียนเทสต์ให้ token หมดอายุ ผู้รับผิด ลายเซ็นถูกแก้ และ nonce ซ้ำ ถูกปฏิเสธทุกกรณี ส่วนความลับของ channel เวลาหมุนเวียนต้องอัปเดตทุกสภาพแวดล้อม แนวทางอยู่ใน เรื่องหมุนเวียนความลับของ CAPI
อาการที่เจอบ่อยและสาเหตุ
| อาการ | สาเหตุที่น่าสงสัยก่อน |
|---|---|
| ตรวจไม่ผ่านแบบสุ่ม | นาฬิกาเซิร์ฟเวอร์เพี้ยน เวลาหมดอายุจึงคลาดเคลื่อน |
| ผู้รับไม่ตรงทุกครั้ง | ใช้ Channel ID ของ Messaging API แทนของ Login channel |
| ไม่พบ nonce ที่เก็บไว้ | เซสชันหายเพราะ cookie ข้ามโดเมนหรือตั้ง SameSite เข้มเกินไป |
| ใช้ได้ในเครื่องทดสอบแต่ล้มบน production | ใช้ Channel ID หรือ secret จากสภาพแวดล้อมคนละชุด หรือเพิ่งหมุนเวียนความลับแล้วยังไม่อัปเดตทุกที่ |
สรุป
ก่อนเปิดใช้ ลองยิงคำขอปลอมใส่ endpoint ของคุณเองด้วย token ที่แก้ผู้รับ และ token ที่ใช้ซ้ำ ถ้ามีกรณีไหนผ่านได้ แปลว่ายังมีรูให้ใครก็ปลอมตัวได้
ถ้ามีข้อสงสัยเรื่องชื่อ claim หรืออัลกอริทึม ให้ยึดเอกสาร LINE Developers ไม่ใช่ตัวอย่างโค้ดเก่าจากอินเทอร์เน็ต
- ห้ามรับ userId ตรง ๆ จากเบราว์เซอร์ ให้รับ id_token แล้วตรวจที่เซิร์ฟเวอร์
- ตรวจลายเซ็น ผู้ออก ผู้รับ อายุ และ nonce ที่ใช้ได้ครั้งเดียว
- ไม่บันทึก token ดิบลง log
- เขียนเทสต์ให้ token ผิดทุกแบบถูกปฏิเสธ
คำถามที่พบบ่อย
ใช้ access token แทน id_token ได้ไหม
ได้ในบางกรณี เช่น เพื่อเรียกดูโปรไฟล์ แต่ access token กับ id_token มีหน้าที่ต่างกัน ต้องตรวจให้ตรงกับชนิดที่ใช้ รายละเอียดดูที่เอกสารของ LINE Developers
token หมดอายุเร็วไปจนล็อกอินล้ม ต้องทำยังไง
ให้ล็อกอินใหม่ ไม่ควรยืดเวลาในโค้ดเอง และตรวจว่านาฬิกาเซิร์ฟเวอร์ตรงเวลามาตรฐานก่อน
ถ้าใช้ LIFF ต้องตรวจ id_token ด้วยไหม
ถ้าฝั่งเซิร์ฟเวอร์ใช้ค่าตัวตนจาก LIFF เป็นฐานตัดสินใจ ก็ต้องตรวจเช่นกัน อย่ารับค่าโปรไฟล์ที่ฝั่งหน้าเว็บส่งมาตรง ๆ ตรวจชื่อ method ของ SDK เวอร์ชันล่าสุดในเอกสาร
เก็บ id_token ไว้ใช้ภายหลังได้ไหม
ไม่ควร เพราะมีอายุสั้นและเป็นข้อมูลที่ใช้ยืนยันตัวตน เก็บผลที่ตรวจผ่านแล้วอย่าง userId กับเวลาที่ยืนยันจะปลอดภัยกว่า
ต้องตรวจทุกคำขอ หรือตรวจครั้งเดียวตอนล็อกอิน
ตรวจตอนล็อกอิน แล้วใช้เซสชันของเราเองสำหรับคำขอต่อมา อย่าลืมกำหนดอายุเซสชันให้เหมาะกับความอ่อนไหวของข้อมูล
อ่านต่อแบบเจาะลึก
วัด Conversion LINE ทุกแบรนด์ รวมศูนย์ที่เดียว
องค์กรที่มีหลายแบรนด์ หลายสาขา หลาย LINE OA linli รวม Tracking และส่ง Conversion กลับทุกแพลตฟอร์ม พร้อม Custom Limit เริ่มฟรี 14 วัน
ติดต่อทีม Sales