OTP เก่า ต่างจาก OTP หมดอายุและ OTP ที่ใช้แล้วอย่างไร?

OTP คือรหัสสำหรับยืนยันแบบใช้ครั้งเดียว แต่คำว่า “เก่า” ในงานทดสอบอาจหมายถึงคนละปัญหา รหัสอาจยังไม่หมดอายุ แต่เป็นของคำขอที่ถูกแทนที่ไปแล้ว หรือเป็นรหัสของคำขอปัจจุบันที่ใช้สำเร็จไปแล้วก็ได้

สำหรับแอปที่คุณควบคุมระบบออกและตรวจรหัสเอง ให้กำหนดนโยบายให้ชัดก่อนเขียนชุดทดสอบ ตัวอย่างในบทนี้เลือกนโยบาย เมื่อขอรหัสใหม่ ให้คำขอใหม่แทนที่คำขอเดิม ภายในผู้ใช้ วัตถุประสงค์ และบริบทเซสชันเดียวกัน

ประเภทของรหัสความหมายในตัวอย่างนี้ผลที่ต้องการ
รหัสจากคำขอก่อนหน้ามีคำขอใหม่มาแทนที่แล้ว แม้ยังไม่ถึงเวลาหมดอายุปฏิเสธ
รหัสหมดอายุเวลาฝั่งเซิร์ฟเวอร์ถึงหรือเลยจุดหมดอายุปฏิเสธ
รหัสที่ใช้แล้วเคยยืนยันสำเร็จด้วยรหัสของคำขอนี้ปฏิเสธ
รหัสปัจจุบันตรงคำขอ ยังไม่หมดอายุ และยังไม่ถูกใช้รับได้เมื่อผ่านเงื่อนไขอื่นครบ

แยกเวลาอย่างน้อยเป็นเวลาเริ่มคำขอ เวลาออกรหัส เวลาหมดอายุ และเวลาที่อีเมลมาถึง อย่าใช้เวลาที่อีเมลมาถึงแทนเวลาออกรหัสหรือเริ่มนับอายุรหัสใหม่เมื่ออ่านอีเมลได้

ใน เอกสาร message ของ Microsoft Graph ฟิลด์ receivedDateTime หมายถึงเวลาที่ได้รับข้อความ ส่วน sentDateTime คือเวลาที่ส่งข้อความ ทั้งคู่เป็นเวลาเกี่ยวกับอีเมล ไม่ใช่เวลาหมดอายุของคำขอยืนยันในแอปคุณ

ลองกำหนดกรณีทดสอบให้ขอรหัสรอบ A แล้วขอรอบ B จากนั้นส่งอีเมลของ B ให้ถึงก่อนอีเมลของ A รหัสทั้งสองแสดงเป็น [ปกปิด] ในบันทึก เมื่ออีเมล A มาถึงทีหลัง ชุดทดสอบต้องไม่ถือว่า A กลับมาเป็นคำขอปัจจุบันเพียงเพราะอยู่บนสุดของรายการ

ลำดับตัวอย่างที่อีเมลของคำขอ A มาถึงหลัง B แต่เซิร์ฟเวอร์ยังปฏิเสธรหัสของ A
ตัวอย่างตามนโยบายแทนที่คำขอ: อีเมลที่มาถึงทีหลังไม่ได้ทำให้คำขอเก่ากลับมาใช้ได้

ต้องตรวจอะไรที่เซิร์ฟเวอร์ก่อนรับ OTP?

อย่าให้การเลือกอีเมลถูกฉบับเป็นเงื่อนไขความปลอดภัยเพียงชั้นเดียว แม้ชุดทดสอบจะเลือกผิด เซิร์ฟเวอร์ก็ควรปฏิเสธรหัสที่ใช้ไม่ได้อยู่ดี สำหรับแอปของคุณเอง ให้ออกแบบการตรวจรับตามลำดับนี้

  1. สร้างคำขอยืนยันที่มีบริบทชัดเจน ผูกคำขอกับผู้ใช้ วัตถุประสงค์ เช่น การเข้าสู่ระบบ และบริบทเซสชันตามการออกแบบของแอป อย่าค้นหาเพียงว่ามีรหัสตัวเลขตรงกันอยู่ที่ใดในระบบ
  2. กำหนดอายุและนโยบายการขอใหม่ ใช้เวลาของเซิร์ฟเวอร์กำหนดจุดหมดอายุ สำหรับนโยบายตัวอย่างนี้ การเปิดคำขอใหม่ต้องทำให้คำขอก่อนหน้าในบริบทเดียวกันใช้ยืนยันต่อไม่ได้ ไม่ใช่แค่เปลี่ยนรหัสที่แสดงในหน้าเว็บ
  3. ตรวจเงื่อนไขก่อนยอมรับ คำขอต้องยังเป็นคำขอปัจจุบัน รหัสต้องตรง ยังไม่หมดอายุ และยังไม่เคยใช้สำเร็จ พร้อมจำกัดจำนวนครั้งที่ลองตามนโยบายของแอป ตรวจบริบทผู้ใช้และวัตถุประสงค์ด้วย ไม่ใช่ตรวจรหัสอย่างเดียว
  4. ตรวจและบันทึกการใช้แบบอะตอมมิก ให้การตัดสินว่ารหัสยังใช้ได้กับการเปลี่ยนสถานะเป็นใช้แล้วเกิดในธุรกรรมเดียวกัน หรือใช้การอัปเดตแบบมีเงื่อนไขที่ให้ผู้ชนะได้เพียงคำขอเดียว อย่าอ่านว่า “ยังไม่ใช้” แล้วค่อยบันทึกภายหลังโดยไม่มีการป้องกันคำขอพร้อมกัน

คำว่า “ปัจจุบัน” “ถูกแทนที่” และ “ใช้แล้ว” ในขั้นตอนนี้เป็นสถานะที่คุณออกแบบในแอป ไม่ใช่ฟิลด์ของ Mailera หากการขอรหัสใหม่ชนกับการยืนยันรหัสเดิม การตรวจว่าคำขอยังเป็นปัจจุบันก็ควรอยู่ในขอบเขตการทำงานแบบอะตอมมิกเดียวกันด้วย

ระบุเงื่อนไขเวลาหมดอายุในข้อกำหนดของแอปให้ชัดเจน เช่น ตัวอย่างนี้ยอมรับเฉพาะเมื่อเวลาปัจจุบันน้อยกว่าเวลาหมดอายุ ดังนั้นเมื่อเวลาเท่ากันต้องปฏิเสธ ใช้กติกาเดียวกันในโค้ดและชุดทดสอบ

ข้อกำหนด TOTP ใน RFC 6238 ระบุว่าผู้ตรวจต้องไม่รับ OTP เดิมซ้ำหลังยืนยันสำเร็จครั้งแรก เอกสารนี้นิยามอัลกอริทึม TOTP ที่อิงเวลา ไม่ใช่ข้อกำหนดอายุของรหัสทุกชนิดที่ส่งทางอีเมล สำหรับระบบออก OTP ของคุณ ให้กำหนดอายุและเงื่อนไขการใช้ครั้งเดียวไว้อย่างชัดเจน แทนการยืมช่วงเวลาของ TOTP มาใช้โดยอัตโนมัติ

Mailera อ่านอีเมล ชุดทดสอบจับคู่คำขอ และเซิร์ฟเวอร์ตรวจเงื่อนไขพร้อมบันทึกการใช้แบบอะตอมมิก
การอ่านรหัสกับการรับรองว่ารหัสใช้ได้เป็นคนละหน้าที่ การตรวจและบันทึกการใช้ต้องไม่เปิดช่องให้คำขอพร้อมกันผ่านทั้งคู่

จะใช้ Mailera อ่าน OTP โดยไม่หยิบรหัสจากรอบก่อนอย่างไร?

Mailera เชื่อมกล่องจดหมายที่คุณมีอยู่ ให้แอปอ่านผ่าน API ได้ แต่ไม่สร้างกล่องจดหมายหรือที่อยู่อีเมลใหม่ให้ เตรียมกล่องจดหมายทดสอบของคุณเองก่อน หากยังไม่ได้วางขั้นตอนอ่านรหัส ดู การอ่าน OTP จากกล่องจดหมายของตนเอง สำหรับภาพรวม

เตรียมสิทธิ์และบริบทก่อนสั่งส่งรหัส

  1. ไปที่ การตั้งค่า แล้วเลือก สร้าง API key กำหนด ขอบเขตกล่องจดหมาย ให้เป็นกลุ่มที่ใช้ทดสอบ และให้สิทธิ์ read ซึ่งเป็นสิทธิ์ที่ getOtp ต้องใช้ งานอ่านรหัสไม่จำเป็นต้องมีสิทธิ์ send หรือ manage
  2. ก่อนสั่งให้แอปส่ง OTP ให้ชุดทดสอบเก็บตัวระบุรอบทดสอบ ตัวระบุคำขอ กล่องจดหมายปลายทาง และเวลาเริ่มคำขอไว้ เวลาเริ่มนี้ช่วยคัดเลือกอีเมล แต่ไม่ใช่นาฬิกาที่ตัดสินอายุรหัส
  3. ถ้าคุณควบคุมแม่แบบอีเมล ให้ใส่ตัวระบุคำขอที่ไม่เป็นความลับในหัวเรื่องหรือเนื้อหา เพื่อให้ชุดทดสอบจับคู่กลับไปยังคำขอได้ชัดเจน ผู้ส่งกับหัวเรื่องทั่วไปเพียงอย่างเดียวอาจแยกรอบ A ออกจากรอบ B ไม่ได้

รายละเอียดการใช้ REST API อยู่ใน หน้าสำหรับนักพัฒนา ส่วนขั้นตอนเตรียมการเชื่อมต่ออ่านได้ใน คู่มือรหัสผ่านสำหรับแอปของกล่องจดหมายเอเจนต์ หากใช้ Outlook ให้ดู การอ่านรหัสยืนยัน Outlook ผ่าน API

อ่านรหัส แล้วตรวจว่าเป็นของคำขอใด

GET /mailboxes/{id}/otp เป็น operation getOtp สำหรับอ่าน OTP ล่าสุด และรับพารามิเตอร์ from, subject และ since_minutes ดูรายละเอียดปลายทางใน เอกสาร API ของ Mailera

กำหนด $MAILERA_API_KEY และ $MAILBOX_ID ในสภาพแวดล้อมของคุณก่อนใช้ตัวอย่าง เปลี่ยนผู้ส่งและหัวเรื่องสมมติให้ตรงกับอีเมลทดสอบของคุณ ตัวอย่างนี้ส่ง since_minutes=5

curl --get "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/otp" \
  -H "Authorization: Bearer $MAILERA_API_KEY" \
  --data-urlencode "from=otp@example.com" \
  --data-urlencode "subject=OTP test" \
  --data-urlencode "since_minutes=5"

เมื่ออ่านรหัสได้ ให้แอปของคุณตรวจคำขอปัจจุบัน เวลาหมดอายุ และสถานะการใช้รหัสแยกต่างหาก คำว่า “ล่าสุด” ไม่ควรเป็นเหตุผลให้ข้ามการจับคู่คำขอ

เมื่อต้องตรวจบริบทเพิ่มเติม ให้อ่านรายการข้อความด้วย GET /mailboxes/{id}/messages ตัวอย่างนี้เลือกโฟลเดอร์กล่องจดหมายเข้าเท่านั้น

curl --get "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/messages" \
  -H "Authorization: Bearer $MAILERA_API_KEY" \
  --data-urlencode "folder=inbox" \
  --data-urlencode "from=otp@example.com"

ในผลลัพธ์ data[] มี id, mailbox_id, from, to, subject, received_at และ is_unread ให้ตรวจกล่องจดหมายและคู่ผู้ส่งผู้รับก่อน แล้วใช้หัวเรื่องหรือเนื้อหาจับคู่กับคำขอ หากผลลัพธ์มี next_cursor และยังต้องอ่านต่อ ให้ส่งค่านั้นกลับในพารามิเตอร์ cursor

นำ id ของข้อความที่ต้องตรวจมาใส่ใน $MESSAGE_ID แล้วอ่านเนื้อหา:

curl "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/messages/$MESSAGE_ID" \
  -H "Authorization: Bearer $MAILERA_API_KEY"

เนื้อหาอยู่ใน data.body.text และ data.body.html ใช้ตัวระบุที่แอปใส่ไว้จับคู่คำขอ ไม่ถือว่า data.id ของอีเมลเป็นตัวระบุคำขอยืนยันโดยอัตโนมัติ และอย่าเก็บผลลัพธ์ทั้งฉบับลงบันทึก CI เพราะอาจมีรหัสจริงอยู่ในเนื้อหา

หากระบุคำขอไม่ได้ ให้หยุดรอบทดสอบพร้อมเหตุผลว่าแยกคำขอไม่ได้ อย่าเดาเลือกรหัส และอย่าย้อนกลับไปหยิบรหัสของรอบก่อนเพื่อให้การทดสอบดำเนินต่อ

กำหนดเวลารอสูงสุดและช่วงเรียกซ้ำไว้ในชุดทดสอบ เมื่อหมดเวลารอให้รายงานว่าไม่พบอีเมลที่จับคู่ได้ แยกจากกรณีที่เซิร์ฟเวอร์ปฏิเสธ OTP สำหรับการนำขั้นตอนนี้ไปใช้กับเบราว์เซอร์ ดู ชุดทดสอบเข้าสู่ระบบด้วย Playwright และกล่องจดหมายเดิม ส่วนการแยกรอบที่ทำงานพร้อมกันอยู่ใน การทดสอบ OTP หลายชุดโดยไม่สลับกล่องจดหมาย

ควรทดสอบกรณีใดเพื่อยืนยันว่า OTP เก่าถูกปฏิเสธ?

แยกผลทดสอบเป็นสองส่วนเสมอ: ชุดทดสอบเลือกอีเมลของคำขอถูกหรือไม่ และเซิร์ฟเวอร์รับหรือปฏิเสธรหัสถูกหรือไม่ การอ่านรหัสได้ไม่ได้หมายความว่าการยืนยันผ่าน และการยืนยันไม่ผ่านก็อาจเกิดจากชุดทดสอบเลือกอีเมลผิด

ตารางนี้ใช้กับนโยบายที่คำขอใหม่แทนที่คำขอเดิม เก็บตัวระบุรอบและเหตุการณ์แทนค่ารหัสจริง

สถานการณ์ลำดับเหตุการณ์ผลที่คาดหวังหลักฐานที่ต้องเก็บ
รหัสปัจจุบันออกคำขอ อ่านอีเมลที่ตรงกัน แล้วยืนยันก่อนหมดอายุยืนยันสำเร็จครั้งเดียวคำขอที่จับคู่ได้ ผลยืนยัน และสถานะหลังใช้
รหัสหมดอายุออกรหัส เลื่อนนาฬิกาทดสอบให้พ้นจุดหมดอายุ แล้วส่งรหัสเซิร์ฟเวอร์ปฏิเสธเวลาอ้างอิงฝั่งเซิร์ฟเวอร์และเหตุผลที่ปฏิเสธ
รหัสที่ใช้แล้วยืนยันสำเร็จ แล้วส่งรหัสของคำขอเดิมอีกครั้งครั้งถัดไปถูกปฏิเสธผลทั้งสองครั้งและจำนวนการยืนยันสำเร็จ
รหัสเดิมหลังขอใหม่ออก A แล้วออก B จากนั้นส่งรหัสของ AA ถูกปฏิเสธเหตุการณ์แทนที่คำขอและผลตรวจ A
อีเมลมาสลับลำดับออก A แล้ว B แต่ให้อีเมล B ถึงก่อน Aชุดทดสอบไม่เลือก A ให้คำขอ Bลำดับที่อีเมลมาถึงและผลจับคู่คำขอ
อ่านอีเมลเดิมซ้ำอ่านข้อความเดิมอีกหลังยืนยันสำเร็จการอ่านซ้ำไม่ทำให้ยืนยันซ้ำสำเร็จตัวระบุข้อความ คำขอ และผลตรวจรับครั้งถัดไป
ยืนยันพร้อมกันส่งรหัสเดียวกันของคำขอที่ยังใช้ได้พร้อมกันสองคำขอสำเร็จเพียงหนึ่งคำขอผลตอบกลับทั้งคู่และสถานะสุดท้ายในฐานข้อมูล
บริบทไม่ตรงส่งรหัสพร้อมบริบทผู้ใช้หรือวัตถุประสงค์อื่นเซิร์ฟเวอร์ปฏิเสธบริบทจำลองและผลตรวจรับโดยไม่เก็บรหัส

สำหรับขอบเวลา ให้ใช้กลไกนาฬิกาที่ควบคุมได้ในตัวตรวจ OTP ของแอป แล้วทดสอบก่อนหมดอายุ ตรงเวลาหมดอายุ และหลังหมดอายุ ตามกติกาตัวอย่าง เฉพาะกรณีก่อนหมดอายุเท่านั้นที่มีสิทธิ์ผ่านเมื่อเงื่อนไขอื่นครบ อย่าใช้การรออีเมลจริงเป็นตัวกำหนดว่าทดสอบขอบเวลาได้ตรงหรือไม่

กรณีส่งพร้อมกันต้องตรวจมากกว่าข้อความตอบกลับ ตรวจด้วยว่ามีการบันทึกความสำเร็จและผลข้างเคียงของการยืนยันเพียงครั้งเดียว ส่วนกรณีอ่านอีเมลซ้ำควรส่งไปยังตัวตรวจของแอปจริงในสภาพแวดล้อมทดสอบ ไม่ใช่เพียงตรวจว่าฟังก์ชันอ่านอีเมลคืนค่าเดิมหรือไม่

ข้อผิดพลาดที่พบบ่อยคืออะไร?

เลือกอีเมลล่าสุดหรืออีเมลที่ยังไม่อ่านอย่างเดียว สถานะ is_unread เป็นข้อมูลของข้อความ อย่าใช้แทนสถานะว่า OTP ยังไม่เคยถูกใช้ ให้จับคู่คำขอและตรวจสถานะการใช้ที่เซิร์ฟเวอร์

พบรหัสแล้วถือว่ายังใช้ได้ หากพบอีเมลจากหลายรอบ ให้แยกด้วยบริบทคำขอ ไม่ใช่เลือกรหัสแรกที่แยกออกมาได้ แล้วให้แอปตรวจเวลาหมดอายุและสถานะการใช้รหัส

ใช้นาฬิกาเครื่องทดสอบตัดสินอายุ OTP ให้เครื่องทดสอบใช้เวลาสำหรับควบคุมการรอ ส่วนอายุรหัสให้ตัวตรวจฝั่งเซิร์ฟเวอร์ตัดสิน เมื่อต้องเปรียบเทียบบันทึกข้ามระบบ ให้ใช้รูปแบบเวลาที่ระบุเขตเวลาอย่างชัดเจน

แยกการตรวจออกจากการบันทึกว่าใช้แล้ว คำขอที่เข้าพร้อมกันอาจต่างอ่านสถานะก่อนมีการบันทึกได้ ออกแบบการตรวจเงื่อนไขและการใช้สิทธิ์ยืนยันให้มีคำขอที่สำเร็จได้เพียงคำขอเดียว แล้วทดสอบกรณีคำขอเข้าพร้อมกันนี้โดยตรง

ถือว่าทุกบริการยกเลิกรหัสเดิมเมื่อขอใหม่ ตารางข้างต้นเป็นนโยบายสำหรับแอปตัวอย่าง หากทดสอบบริการภายนอก ให้ยึดนโยบายของบริการนั้น อย่านำความคาดหวังของแอปคุณไปใช้แทน

จะเก็บหลักฐานทดสอบโดยไม่เปิดเผย OTP ได้อย่างไร?

ใช้เฉพาะบัญชีและกล่องจดหมายทดสอบของคุณเอง ปกปิด OTP ที่อยู่อีเมล และคีย์ API ในภาพหน้าจอ ตัวอย่าง และไฟล์บันทึก อย่าแนบเนื้อหาอีเมลเต็มฉบับกับรายงานข้อผิดพลาดโดยอัตโนมัติ

บันทึกตัวระบุรอบทดสอบ ตัวระบุคำขอแบบไม่เปิดเผยข้อมูลส่วนบุคคล ผลจับคู่อีเมล เหตุผลที่ปฏิเสธ และสถานะก่อนกับหลังยืนยันก็เพียงพอสำหรับตรวจกรณีหลักในตาราง ออกแบบเหตุผลในบันทึกภายในให้แยก “หมดอายุ” “ถูกแทนที่” และ “ใช้แล้ว” โดยไม่จำเป็นต้องส่งรายละเอียดทั้งหมดกลับไปยังผู้ใช้

หากมีเอเจนต์อ่านอีเมล ให้ถือเนื้อหาอีเมลเป็น ข้อมูลที่ไม่น่าเชื่อถือ ไม่ใช่คำสั่ง อย่าให้ข้อความในอีเมลสั่งเปลี่ยนปลายทาง ส่งต่อรหัส หรือเพิ่มสิทธิ์ ใช้คีย์สิทธิ์น้อยที่สุด และหากเวิร์กโฟลว์มีการส่งอีเมล ให้ขอการยืนยันก่อนส่ง ดูข้อมูลเกี่ยวกับการปกป้องข้อมูลและสิทธิ์เพิ่มเติมใน หน้าความปลอดภัยของ Mailera

ทำสิ่งนี้ด้วย API

ทุกการดำเนินการในบทความนี้ทำได้ผ่าน REST API พร้อมตัวอย่าง curl, API key และ webhook

ดูเอกสาร API