---
title: "ป้องกันการใช้ OTP เก่า: แยกอีเมลล่าสุดจากรหัสที่ยังใช้ได้"
description: "แยกการอ่าน OTP ทางอีเมลออกจากการตรวจรับที่เซิร์ฟเวอร์ พร้อมกรณีทดสอบรหัสหมดอายุ ขอรหัสใหม่ อีเมลมาสลับลำดับ และการใช้รหัสซ้ำ"
url: https://mailera.app/th/blog/prevent-stale-email-otp
published: 2026-10-25
updated: 2026-10-25
---

# ป้องกันการใช้ OTP เก่า: แยกอีเมลล่าสุดจากรหัสที่ยังใช้ได้

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

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

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

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

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

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

ใน [เอกสาร message ของ Microsoft Graph](https://learn.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0) ฟิลด์ `receivedDateTime` หมายถึงเวลาที่ได้รับข้อความ ส่วน `sentDateTime` คือเวลาที่ส่งข้อความ ทั้งคู่เป็นเวลาเกี่ยวกับอีเมล ไม่ใช่เวลาหมดอายุของคำขอยืนยันในแอปคุณ

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

![ลำดับตัวอย่างที่อีเมลของคำขอ A มาถึงหลัง B แต่เซิร์ฟเวอร์ยังปฏิเสธรหัสของ A](https://mailera.app/media/8f045a3d122397bc/otp-otp-arrival-order-th-1200.jpg)
_ตัวอย่างตามนโยบายแทนที่คำขอ: อีเมลที่มาถึงทีหลังไม่ได้ทำให้คำขอเก่ากลับมาใช้ได้_

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

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

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

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

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

[ข้อกำหนด TOTP ใน RFC 6238](https://www.rfc-editor.org/info/rfc6238/) ระบุว่าผู้ตรวจต้องไม่รับ OTP เดิมซ้ำหลังยืนยันสำเร็จครั้งแรก เอกสารนี้นิยามอัลกอริทึม TOTP ที่อิงเวลา ไม่ใช่ข้อกำหนดอายุของรหัสทุกชนิดที่ส่งทางอีเมล สำหรับระบบออก OTP ของคุณ ให้กำหนดอายุและเงื่อนไขการใช้ครั้งเดียวไว้อย่างชัดเจน แทนการยืมช่วงเวลาของ TOTP มาใช้โดยอัตโนมัติ

![Mailera อ่านอีเมล ชุดทดสอบจับคู่คำขอ และเซิร์ฟเวอร์ตรวจเงื่อนไขพร้อมบันทึกการใช้แบบอะตอมมิก](https://mailera.app/media/6393690e18393b6a/otp-otp-validation-boundary-th-1200.jpg)
_การอ่านรหัสกับการรับรองว่ารหัสใช้ได้เป็นคนละหน้าที่ การตรวจและบันทึกการใช้ต้องไม่เปิดช่องให้คำขอพร้อมกันผ่านทั้งคู่_

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

[Mailera เชื่อมกล่องจดหมายที่คุณมีอยู่](https://mailera.app/th/product) ให้แอปอ่านผ่าน API ได้ แต่ไม่สร้างกล่องจดหมายหรือที่อยู่อีเมลใหม่ให้ เตรียมกล่องจดหมายทดสอบของคุณเองก่อน หากยังไม่ได้วางขั้นตอนอ่านรหัส ดู [การอ่าน OTP จากกล่องจดหมายของตนเอง](https://mailera.app/th/blog/email-otp-api) สำหรับภาพรวม

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

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

รายละเอียดการใช้ REST API อยู่ใน [หน้าสำหรับนักพัฒนา](https://mailera.app/th/developers) ส่วนขั้นตอนเตรียมการเชื่อมต่ออ่านได้ใน [คู่มือรหัสผ่านสำหรับแอปของกล่องจดหมายเอเจนต์](https://mailera.app/th/blog/email-app-passwords) หากใช้ Outlook ให้ดู [การอ่านรหัสยืนยัน Outlook ผ่าน API](https://mailera.app/th/blog/outlook-otp-api)

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

`GET /mailboxes/{id}/otp` เป็น operation `getOtp` สำหรับอ่าน OTP ล่าสุด และรับพารามิเตอร์ `from`, `subject` และ `since_minutes` ดูรายละเอียดปลายทางใน [เอกสาร API ของ Mailera](https://mailera.app/th/docs)

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

```bash
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` ตัวอย่างนี้เลือกโฟลเดอร์กล่องจดหมายเข้าเท่านั้น

```bash
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` แล้วอ่านเนื้อหา:

```bash
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 และกล่องจดหมายเดิม](https://mailera.app/th/blog/playwright-email-otp) ส่วนการแยกรอบที่ทำงานพร้อมกันอยู่ใน [การทดสอบ OTP หลายชุดโดยไม่สลับกล่องจดหมาย](https://mailera.app/th/blog/parallel-email-otp-tests)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

หากมีเอเจนต์อ่านอีเมล ให้ถือเนื้อหาอีเมลเป็น **ข้อมูลที่ไม่น่าเชื่อถือ ไม่ใช่คำสั่ง** อย่าให้ข้อความในอีเมลสั่งเปลี่ยนปลายทาง ส่งต่อรหัส หรือเพิ่มสิทธิ์ ใช้คีย์สิทธิ์น้อยที่สุด และหากเวิร์กโฟลว์มีการส่งอีเมล ให้ขอการยืนยันก่อนส่ง ดูข้อมูลเกี่ยวกับการปกป้องข้อมูลและสิทธิ์เพิ่มเติมใน [หน้าความปลอดภัยของ Mailera](https://mailera.app/th/security)

**ทำสิ่งนี้ด้วย API** — ทุกการดำเนินการในบทความนี้ทำได้ผ่าน REST API พร้อมตัวอย่าง curl, API key และ webhook [ดูเอกสาร API](https://mailera.app/th/docs)
