ทุกครั้งที่ผู้ใช้เข้าสู่ระบบแอปพลิเคชัน SaaS ระบบจะสร้าง
auth token
ขึ้นมาและส่งให้กับเบราว์เซอร์หรือแอปมือถือของผู้ใช้ token นั้นมีนาฬิกาซ่อนอยู่ข้างใน ฟิลด์
JWT expiration
ที่เก็บเป็น Unix timestamp ธรรมดา จะบอกทุก server ที่อ่าน token นั้นว่าควรหยุดเชื่อถือ credential นี้เมื่อไหร่ หากตั้ง timestamp ผิดแม้แค่ไม่กี่วินาที ผู้ใช้อาจถูกตัดสิทธิ์เร็วเกินไป หรือแย่กว่านั้นคือ session ยังคงใช้งานได้นานกว่าที่นโยบายความปลอดภัยกำหนดไว้มาก การเข้าใจว่า claim ใน
JSON web token
ทำงานอย่างไร โดยเฉพาะ
exp
และ
iat
เป็นสิ่งที่ทีม SaaS ควรทำเพื่อเสริมความปลอดภัยของระบบยืนยันตัวตนโดยไม่สร้างความยุ่งยากให้ผู้ใช้ครับ
สรุปสาระสำคัญ:
-
claim
expและiatใน JWT JSON web token เป็น Unix timestamp ที่นับเป็นวินาทีเต็มเสมอ ไม่ใช่มิลลิวินาที - clock skew ระหว่าง server อาจทำให้ token ถูกปฏิเสธโดยไม่รู้ตัว หรือทำให้ session ยืดยาวเกินนโยบาย - การตั้งค่าความคลาดเคลื่อนที่ยอมรับได้ 60 วินาที คือมาตรฐานที่อุตสาหกรรมใช้แก้ปัญหานี้
- ตั้งค่า token expiry นานเกินไปเพิ่มความเสี่ยงด้านความปลอดภัย แต่สั้นเกินไปก็ทำให้ประสบการณ์ใช้งานแย่ลง มีสูตรที่ช่วยเลือกค่าที่เหมาะสมได้
- สามารถถอดรหัสและตรวจสอบ timestamp ใน JSON web token ได้เป็นหน่วยวินาทีผ่าน Unix epoch converter โดยไม่ต้องใช้ไลบรารีใดเลย
สารบัญ
JWT คืออะไร และทำไม Timestamp ถึงอยู่ข้างใน
JSON web token คือ credential ขนาดกะทัดรัดที่ปลอดภัยสำหรับ URL ประกอบด้วยสามส่วนที่เข้ารหัสด้วย Base64 คั่นด้วยจุด ได้แก่ header, payload และ signature ข้อมูลที่มีความไวต่อเวลาจะถูกเก็บไว้ใน payload เนื่องจาก JWT เป็น stateless server จึงไม่ต้องค้นหา session ในฐานข้อมูลเพื่อตรวจสอบความถูกต้อง แต่จะอ่าน claim ที่ฝังอยู่ใน payload และเชื่อถือ cryptographic signature แทน การออกแบบนี้รวดเร็วและรองรับการขยายระบบได้ดี แต่มีข้อจำกัดสำคัญคือ เมื่อออก token ไปแล้ว server ไม่สามารถ "ลบ" token นั้นได้ วิธีเดียวที่เชื่อถือได้ในการจำกัดอายุการใช้งานคือการฝัง expiration timestamp ลงใน token โดยตรงครับ
นี่คือเหตุผลที่เวลา โดยเฉพาะ Unix epoch time มีความสำคัญต่อความปลอดภัยของ JWT ข้อกำหนด RFC 7519 ที่นิยาม JWT กำหนดให้ claim ที่เกี่ยวข้องกับเวลาต้องแสดงเป็นค่า "NumericDate" ซึ่งก็คือจำนวนวินาทีนับตั้งแต่วันที่ 1 มกราคม 1970 UTC ไม่มี timezone ไม่มีการจัดรูปแบบตาม locale เป็นเพียงจำนวนเต็มที่ server ทุกเครื่องทั่วโลกสามารถนำไปเปรียบเทียบกับนาฬิกาของตัวเองได้
อธิบาย claim
exp
และ
iat
ข้อกำหนด JWT กำหนด registered claim ไว้หลายรายการ แต่สองรายการที่สำคัญที่สุดสำหรับการจัดการวงจรชีวิต token คือ:
-
exp(Expiration Time): timestamp ที่ระบุว่าหลังจากเวลานี้ token จะไม่ถูกยอมรับอีกต่อไป server ต้องปฏิเสธ token ทุกรายการที่เวลาปัจจุบันเท่ากับหรือมากกว่าค่านี้ นี่คือกลไกหลักของ JWT expiration -
iat(Issued At): timestamp ที่ระบุเวลาที่สร้าง token claim iat ไม่ถูกบังคับใช้โดยค่าเริ่มต้น แต่มีประโยชน์มาก ช่วยให้คำนวณอายุของ token ได้ บังคับใช้นโยบายอายุสูงสุดได้อิสระจากexpและตรวจจับ token ที่ออกมาในอดีตอย่างน่าสงสัยได้
claim ที่สาม คือ
nbf
(Not Before) กำหนดเวลาเริ่มต้นที่ token จะเริ่มมีผล ใช้น้อยกว่าแต่ใช้รูปแบบ Unix timestamp เดียวกัน
ทั้งสามค่าเป็นจำนวนเต็ม หากระบบของคุณสร้างค่าเหล่านี้เป็นมิลลิวินาที ซึ่งเป็นข้อผิดพลาดที่พบบ่อยเมื่อใช้
Date.now()
ของ JavaScript ตัวเลขที่ได้จะใหญ่กว่าที่ควรประมาณ 1,000 เท่า server ที่ตรวจสอบ
exp
กับ epoch second ปัจจุบัน จะเห็น token ที่ดูเหมือนจะหมดอายุในปี ค.ศ. 33,658 และจะไม่ปฏิเสธ token นั้นเลย นี่คือบัก production จริงที่เคยเกิดขึ้นกับทีม SaaS หลายทีมครับ
ตัวอย่างจริง: ถอดรหัส Timestamp ใน Token
สมมติว่าบริการยืนยันตัวตนของคุณออก JWT payload ต่อไปนี้หลังจากผู้ใช้เข้าสู่ระบบเวลา 10:00 น. UTC วันที่ 1 กรกฎาคม 2025:
{
"sub": "user_8821",
"iat": 1751364000,
"exp": 1751367600,
"role": "admin"
}
มาดูกันว่าตัวเลขเหล่านี้หมายความว่าอะไร:
| Claim | Unix Timestamp | วันเวลาที่อ่านได้ (UTC) | ความหมาย |
|---|---|---|---|
iat
|
1751364000 | 2025-07-01 10:00:00 | เวลาที่ออก token |
exp
|
1751367600 | 2025-07-01 11:00:00 | token หมดอายุหลังจากนั้น 1 ชั่วโมงพอดี |
ผลต่างคือ 3,600 วินาทีพอดี ซึ่งเท่ากับหนึ่งชั่วโมง server ใดก็ตามที่ได้รับ token นี้หลังเวลา 11:00:00 UTC จะต้องปฏิเสธ token นั้น หากต้องการตรวจสอบด้วยตนเอง ให้ลบ
iat
ออกจาก
exp
: 1751367600 - 1751364000 = 3,600 วินาที คุณสามารถยืนยันค่าที่อ่านได้ของตัวเลขเหล่านี้ได้ทันทีโดยใช้ Unix timestamp converter ซึ่งเป็นวิธีตรวจสอบเบื้องต้นที่ช่วยป้องกันบักมิลลิวินาที-กับ-วินาทีที่กล่าวถึงก่อนหน้านี้ได้ดีครับ
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่ฐานข้อมูลควรเก็บค่าเหล่านี้ ดูคู่มือของเราเรื่อง
Unix timestamp ในฐานข้อมูล
เนื่องจากรูปแบบจำนวนเต็มเดียวกันนี้ใช้ได้ทั้งการเก็บ
exp
ใน token และในตาราง audit log
Clock Skew: ภัยเงียบที่ทำลาย Token
นี่คือข้อจำกัดจริงที่มักทำให้ทีมต่างๆ ตกใจ ในระบบแบบ distributed authentication server และ API server ของคุณเป็นคนละเครื่องกัน นาฬิกาของระบบทั้งสองซิงค์กันผ่าน NTP แต่ไม่มีทางตรงกันพอดีเสมอ ความแตกต่าง 30 ถึง 90 วินาทีเป็นเรื่องปกติในสภาพแวดล้อม cloud
ลองนึกภาพสถานการณ์นี้: token ที่มี
exp = 1751367600
ถูกตรวจสอบโดย API server ที่นาฬิกาอ่านค่า 1751367610 ซึ่งเลยเวลาหมดอายุไปเพียง 10 วินาที token ถูกปฏิเสธ ผู้ใช้ได้รับ error 401 จากมุมมองของผู้ใช้ พวกเขาเพิ่งเข้าสู่ระบบแล้วแอปก็พัง
วิธีแก้มาตรฐานคือการตั้งค่า clock skew tolerance ในลอจิกการตรวจสอบของคุณ ไลบรารี JWT ส่วนใหญ่รองรับพารามิเตอร์ leeway ค่า 60 วินาทีเป็นที่ยอมรับกันอย่างกว้างขวางและอ้างอิงอยู่ใน ข้อกำหนด OpenID Connect Core ตั้งค่านี้ จดบันทึกไว้ และตรวจสอบให้แน่ใจว่าทุก service ในระบบของคุณใช้ค่าเดียวกันครับ
กฎเชิงปฏิบัติ:
เพิ่ม leeway 60 วินาทีในการตรวจสอบ
exp
อย่าเพิ่ม leeway ในการตรวจสอบ
iat
เด็ดขาด เพราะจะทำให้ยอมรับ token ที่ออกมาจากอนาคตได้ ซึ่งเป็นสัญญาณเตือนของการโจมตีแบบ replay attack
เลือกค่า Token Expiry ที่เหมาะกับผลิตภัณฑ์ SaaS ของคุณ
ค่า token expiry ที่เหมาะสมขึ้นอยู่กับความละเอียดอ่อนของแอปพลิเคชันและพฤติกรรม session ที่คาดหวัง ต่อไปนี้คือแนวทางปฏิบัติ:
- แอปที่มีความละเอียดอ่อนสูง (ธนาคาร, สุขภาพ, แดชบอร์ดผู้ดูแลระบบ): 15 ถึง 30 นาทีสำหรับ access token ใช้ refresh token เพื่อต่ออายุ session โดยอัตโนมัติ
- แอป SaaS ทั่วไป (จัดการโครงการ, CRM, analytics): 1 ถึง 8 ชั่วโมง ให้ตรงกับระยะเวลา session การทำงานทั่วไป
- API ความเสี่ยงต่ำหรือแบบอ่านอย่างเดียว: สูงสุด 24 ชั่วโมง แต่ควรจับคู่กับการหมุนเวียน token เมื่อมีการดำเนินการที่ละเอียดอ่อน
- service token สำหรับการสื่อสารระหว่างเครื่อง (machine-to-machine): อาจนานกว่า (หลายวันหรือหลายสัปดาห์) แต่ควรกำหนดขอบเขตให้แคบและหมุนเวียนตามกำหนดเวลา
สูตรที่ใช้งานได้จริงสำหรับคำนวณค่า
exp
ณ เวลาที่ออก token:
// Node.js example
const iat = Math.floor(Date.now() / 1000); // เวลาปัจจุบันในหน่วยวินาที
const ttl = 3600; // time-to-live: 1 ชั่วโมงในหน่วยวินาที
const exp = iat + ttl;
const payload = {
sub: userId,
iat: iat,
exp: exp
};
สังเกตการหารด้วย 1,000 อย่างชัดเจนเพื่อแปลงจากมิลลิวินาทีเป็นวินาที โค้ดบรรทัดเดียวนี้ป้องกันบัก JWT timestamp ที่พบบ่อยที่สุดในแอปพลิเคชัน JavaScript ได้ครับ
ขั้นตอนปฏิบัติสำหรับการตั้งค่า JWT Expiration ที่ปลอดภัย
นี่คือ checklist ที่นำไปใช้กับระบบยืนยันตัวตน SaaS ได้ทันที:
-
ใส่ทั้ง
iatและexpเสมอ claimiatเป็นตัวเลือกทางเทคนิค แต่ให้ audit trail และช่วยบังคับใช้นโยบายอายุสูงสุดได้อิสระจากการหมดอายุ -
ตรวจสอบรูปแบบ timestamp ก่อน deploy
ถอดรหัส token และตรวจสอบว่า
expเป็นจำนวนเต็ม 10 หลัก หากเป็น 13 หลัก แสดงว่ามีมิลลิวินาทีแฝงอยู่ - ตั้งค่า clock skew leeway 60 วินาที ใน validation middleware ใช้ค่าเดียวกันอย่างสม่ำเสมอในทุก service ที่รับ auth token
- ใช้ access token อายุสั้นร่วมกับ refresh token access token อายุ 15 นาทีคู่กับ refresh token อายุ 7 วัน ปลอดภัยกว่า access token อายุ 7 วันเพียงอย่างเดียวมาก เพราะ refresh token สามารถยกเลิกได้ฝั่ง server
-
บันทึกค่า
iatในทุกคำขอที่ผ่านการยืนยันตัวตน ช่วยให้ตรวจจับความผิดปกติได้ เช่น token เดียวกันถูกใช้จากสองภูมิภาคพร้อมกัน -
ทดสอบการหมดอายุใน staging ด้วยการเร่งเวลา
จำลองนาฬิกาให้กระโดดผ่านค่า
expและตรวจสอบว่าแอปของคุณจัดการ error 401 ได้อย่างสวยงาม ไม่ใช่หยุดทำงานหรือวนซ้ำ
หากต้องการแปลงค่า
exp
ดิบจาก token เป็นวันที่ที่อ่านได้อย่างรวดเร็ว หรือตรวจสอบว่าควรตั้ง timestamp เท่าไหร่สำหรับระยะเวลาที่กำหนด
เครื่องมือแปลง epoch บนหน้าหลักของเรา
รองรับทั้งสองทิศทางได้ทันที ไม่ต้องเขียนโค้ดเลยครับ
สรุป
JWT expiration ไม่ใช่แนวคิดที่ซับซ้อน แต่เป็นจุดที่ข้อผิดพลาดเล็กน้อยสร้างช่องโหว่ด้านความปลอดภัยขนาดใหญ่ได้ claim
exp
และ
iat
เป็นเพียงจำนวนเต็ม ซึ่งก็คือ Unix timestamp ที่นับเป็นวินาทีจาก epoch ความเรียบง่ายนี้คือจุดแข็ง: ทุก server ทุกภาษา และทุกแพลตฟอร์มสามารถเปรียบเทียบตัวเลขสองตัวได้โดยไม่คลุมเครือ ทักษะที่แท้จริงอยู่ที่การนำไปใช้อย่างถูกต้อง ไม่ว่าจะเป็นการเลือกระยะเวลา
token expiry
ที่สมเหตุสมผล การจัดการ clock skew ด้วย tolerance window และการตรวจจับข้อผิดพลาดมิลลิวินาที-กับ-วินาทีก่อน deploy สร้างนิสัยเหล่านี้ใน auth pipeline ของคุณ และการใช้งาน
JSON web token
ของคุณจะทั้งปลอดภัยและดูแลรักษาได้ง่ายครับ
ตรวจสอบ JWT Timestamp ได้ทันที - ไม่ต้องเขียนโค้ด
วาง Unix timestamp จาก JWT payload แล้วแปลงเป็นวันที่ที่อ่านได้ด้วยคลิกเดียว ตรวจจับข้อผิดพลาดมิลลิวินาที-กับ-วินาทีก่อนถึง production
ลองใช้เครื่องมือฟรีของเรา →
exp
(Expiration Time) คือ Unix timestamp ที่ระบุว่าหลังจากเวลานี้ token ไม่ถูกต้องและต้องถูกปฏิเสธ ส่วน
iat
(Issued At) คือ timestamp ที่ระบุเวลาที่สร้าง token ทั้งสองค่าเป็นจำนวนเต็มในหน่วยวินาที claim
iat
เป็นตัวเลือก แต่มีประโยชน์สำหรับการ audit และการบังคับใช้นโยบายอายุสูงสุดของ token
Unix timestamp เป็นจำนวนเต็มที่ไม่ขึ้นอยู่กับ timezone ทำให้ไม่มีความคลุมเครือในทุก server และทุกภูมิภาค สตริงวันที่ที่จัดรูปแบบแล้ว เช่น "2025-07-01T10:00:00" อาจถูกตีความผิดตาม locale หรือการตั้งค่า timezone การเปรียบเทียบจำนวนเต็มยังรวดเร็วกว่าและเกิดข้อผิดพลาดน้อยกว่าการ parse สตริงในระบบยืนยันตัวตนที่มีปริมาณงานสูงด้วยครับ
token ที่มีอายุยาวจะยังคงใช้งานได้แม้บัญชีผู้ใช้จะถูกบุกรุกหรือสิทธิ์เปลี่ยนแปลงไปแล้ว เนื่องจาก JWT เป็น stateless server จึงไม่สามารถยกเลิก token กลางคันได้ หากผู้โจมตีขโมย token ที่มีอายุ 30 วัน พวกเขาจะมีสิทธิ์เข้าถึงได้นาน 30 วัน การใช้ช่วงเวลาหมดอายุสั้นร่วมกับ refresh token ช่วยลดช่วงเวลาความเสี่ยงนี้ได้อย่างมีนัยสำคัญครับ
ใน JavaScript ให้ใช้
Math.floor(Date.now() / 1000)
เสมอเมื่อสร้างค่า
iat
หรือ
exp
เนื่องจาก
Date.now()
ดิบคืนค่าเป็นมิลลิวินาที การหารด้วย 1,000 จะแปลงเป็นจำนวนเต็มหน่วยวินาทีตามที่ข้อกำหนด JWT กำหนด ตรวจสอบได้โดยดูว่า timestamp ของคุณเป็นตัวเลข 10 หลัก ไม่ใช่ 13 หลักครับ
clock skew คือความแตกต่างเล็กน้อยระหว่างนาฬิกาของ server สองเครื่องในระบบแบบ distributed แม้แค่ 30 วินาทีก็อาจทำให้ token ที่ถูกต้องถูกปฏิเสธ หาก server ที่ตรวจสอบมีนาฬิกาเดินเร็วกว่าเล็กน้อย วิธีแก้มาตรฐานคือการตั้งค่า leeway 60 วินาทีในไลบรารี JWT validation เพื่อรองรับ NTP drift ปกติครับ