JWT Tokens อธิบายแบบละเอียด: โครงสร้าง ความปลอดภัย และข้อผิดพลาดที่พบบ่อย
ทำความเข้าใจว่า JSON Web Tokens ทำงานอย่างไร มีอะไรอยู่ภายใน และกับดักด้านความปลอดภัยที่นักพัฒนามักพลาดในระบบจริง
JSON Web Tokens (JWTs) อยู่ทุกที่ — เป็นกลไกหลักของการยืนยันตัวตนใน SPAs, แอปมือถือ และสถาปัตยกรรม microservice ทั่วโลก แต่ในขณะเดียวกัน ก็เป็นหนึ่งในพื้นฐานด้านความปลอดภัยที่นักพัฒนาเข้าใจผิดกันมากที่สุด หากใช้งานผิดพลาด อาจนำไปสู่การข้ามผ่านการยืนยันตัวตน, การยกระดับสิทธิ์, หรือการเข้าครอบครองบัญชีได้อย่างสมบูรณ์
JWT คืออะไร?
JWT คือสตริงขนาดกะทัดรัดและปลอดภัยสำหรับ URL ที่เข้ารหัสชุด claims — ข้อมูลยืนยันเกี่ยวกับผู้ใช้หรือ session โดยมีหน้าตาดังนี้:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
ประกอบด้วยสามส่วนที่เข้ารหัสด้วย Base64URL คั่นด้วยจุด:
- Header — อัลกอริทึมและประเภท token
- Payload — ข้อมูล claims (ข้อมูลผู้ใช้)
- Signature — หลักฐานการเข้ารหัสเพื่อยืนยันความสมบูรณ์ของข้อมูล
นำ JWT ใดก็ได้ไปวางใน JWT Decoder ของเรา เพื่อตรวจสอบทั้งสามส่วนได้ทันทีโดยไม่ต้องติดต่อ server ภายนอกใดๆ
โครงสร้างของ JWT
Header
{
"alg": "HS256",
"typ": "JWT"
}
alg ระบุอัลกอริทึมที่ใช้เซ็น ค่าที่พบบ่อย:
HS256— HMAC กับ SHA-256 (symmetric ใช้ secret เดียว)RS256— RSA กับ SHA-256 (asymmetric มี public/private key)ES256— ECDSA กับ SHA-256 (asymmetric ขนาด key เล็กกว่า)
Payload
{
"sub": "user_abc123",
"email": "alice@example.com",
"role": "admin",
"iat": 1711670400,
"exp": 1711756800
}
Claims มาตรฐานที่ลงทะเบียนไว้:
| Claim | ความหมาย |
|---|---|
sub |
Subject (token นี้เกี่ยวกับใคร) |
iss |
Issuer (ใครเป็นผู้สร้าง token) |
aud |
Audience (ใครควรรับ token นี้) |
exp |
เวลาหมดอายุ (Unix timestamp) |
iat |
เวลาที่ออก token (Unix timestamp) |
nbf |
Not before (ห้ามยอมรับก่อนเวลานี้) |
Signature
สำหรับ HS256:
HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)
Signature เป็นหลักฐานว่า token ไม่ได้ถูกแก้ไข Payload ไม่ได้ถูกเข้ารหัส — เพียงแค่ถูก encode เท่านั้น ทุกคนสามารถอ่านได้
อย่าเก็บข้อมูลสำคัญอย่างรหัสผ่านหรือหมายเลขบัตรเครดิตไว้ใน JWT payload เป็นอันขาด
HS256 กับ RS256: เลือกอะไรดี?
HS256 ใช้ secret ร่วมกันเพียงตัวเดียว ทุก service ที่ต้องการตรวจสอบ token ต้องมี secret เดียวกัน เหมาะสำหรับระบบ monolith แต่เสี่ยงในสถาปัตยกรรมแบบ multi-service — หากมี service ใดถูกเจาะ ผู้โจมตีสามารถปลอมแปลง token ได้
RS256 ใช้ asymmetric keys โดย auth server เซ็นด้วย private key และ service อื่นๆ ตรวจสอบด้วย public key การที่ service ผู้ใช้งานถูกเจาะไม่ได้ทำให้ผู้โจมตีสามารถปลอมแปลง token ได้ แนะนำให้ใช้ RS256 สำหรับระบบที่มีหลาย service
ช่องโหว่ alg: none
เป็นหนึ่งในการโจมตี JWT ที่โด่งดังที่สุด library เก่าบางตัวยอมรับ token ที่มี "alg": "none" และไม่มี signature — โดยถือว่าถูกต้อง ผู้โจมตีสามารถสร้าง:
{ "alg": "none" }
พร้อม payload ใดก็ได้ และข้ามการยืนยันตัวตนได้อย่างสมบูรณ์
วิธีแก้: ระบุอัลกอริทึมที่อนุญาตอย่างชัดเจนใน JWT library เสมอ และห้ามยอมรับ none
// ❌ อันตราย
jwt.verify(token, secret);
// ✅ ปลอดภัย — รับเฉพาะ HS256
jwt.verify(token, secret, { algorithms: ["HS256"] });
การโจมตีด้วยการสับสนอัลกอริทึม
อีกหนึ่งช่องโหว่ร้ายแรง: หาก library ตรวจจับอัลกอริทึมจาก header โดยอัตโนมัติ ผู้โจมตีสามารถเปลี่ยน RS256 เป็น HS256 และเซ็น token โดยใช้ public key เป็น HMAC secret (ซึ่งตามนิยามแล้วเป็นข้อมูลสาธารณะ)
วิธีแก้: กำหนดอัลกอริทึมที่คาดหวังอย่างตายตัวเสมอ อย่าเชื่อ alg header
การเก็บ Token: ควรเก็บ JWT ไว้ที่ไหน?
| ที่เก็บ | ความเสี่ยง XSS | ความเสี่ยง CSRF | หมายเหตุ |
|---|---|---|---|
localStorage |
สูง | ไม่มี | JS ทุกตัวบนหน้าเข้าถึงได้ |
sessionStorage |
สูง | ไม่มี | ถูกลบเมื่อปิด tab |
| HTTP-only cookie | ไม่มี | ปานกลาง | ดีที่สุดสำหรับ web app ใช้ SameSite=Strict |
| Memory (ตัวแปร) | ต่ำ | ไม่มี | หายเมื่อ refresh เหมาะสำหรับ SPA |
สำหรับ web application HTTP-only cookies พร้อม SameSite=Strict คือตัวเลือกที่ปลอดภัยที่สุด สำหรับแอปพลิเคชัน native ควรใช้ secure storage API (Keychain, Keystore)
การหมดอายุและ Refresh Tokens
รูปแบบมาตรฐานคือ access token อายุสั้น (5–15 นาที) คู่กับ refresh token อายุยาว:
- ผู้ใช้ล็อกอิน → server ออก access token (15 นาที) + refresh token (7 วัน เก็บใน DB)
- Client ใช้ access token สำหรับเรียก API
- เมื่อ access token หมดอายุ client ส่ง refresh token → รับ access token ใหม่
- เมื่อล็อกเอาต์ ให้ยกเลิก refresh token ใน database
วิธีนี้จำกัดความเสียหายหาก access token ถูกขโมย
การยกเลิก JWT
JWT เป็น stateless — เมื่อออกแล้วจะมีผลจนกว่าจะหมดอายุ นี่คือข้อแลกเปลี่ยน ตัวเลือกสำหรับการยกเลิกก่อนกำหนด:
- Blocklist — เก็บค่า JTI (JWT ID) ที่ถูกยกเลิกใน Redis และตรวจสอบทุก request
- อายุสั้น — token 5 นาทีจำกัดขอบเขตความเสียหาย
- Refresh token rotation — ตรวจจับการใช้ซ้ำของ token ที่ถูก rotate แล้วว่าเป็นสัญญาณการขโมย
การ Debug ด้วย JWT Decoder
เมื่อ debug ปัญหาการยืนยันตัวตน ใช้ JWT Decoder ของเราเพื่อ:
- ตรวจสอบ payload ทั้งหมดโดยไม่ต้องเขียนโค้ด
- ดู expiration timestamp ในรูปแบบที่อ่านง่าย
- ตรวจสอบอัลกอริทึมที่ใช้งานอยู่
- ค้นหา claims ที่ผิดปกติหรือขาดหายไป
การ decode ทั้งหมดเกิดขึ้นใน browser ของคุณ — token ไม่ได้ออกจากเครื่องของคุณ
Checklist ความปลอดภัยของ JWT
- ใช้ RS256 หรือ ES256 สำหรับสถาปัตยกรรมแบบ multi-service
- ระบุอัลกอริทึมที่อนุญาตอย่างชัดเจนใน library ของคุณ
- ตรวจสอบ claims
exp,issและaudในทุก request - เก็บ token ใน HTTP-only cookies สำหรับ web app
- ใช้ access token ที่มีอายุสั้น (15 นาทีหรือน้อยกว่า)
- ใช้ refresh token rotation พร้อมการตรวจจับการใช้ซ้ำ
- อย่าใส่ข้อมูลสำคัญใน payload
- ใช้ HTTPS ทุกที่ — token ที่ส่งแบบ plaintext ก็ไม่ต่างจากไม่มีการยืนยันตัวตนเลย
JWT มีประสิทธิภาพและสะดวกใช้ แต่ความปลอดภัยขึ้นอยู่กับการนำไปใช้งานทั้งหมด ทำความเข้าใจโครงสร้าง รู้จักการโจมตีต่างๆ แล้ว auth layer ของคุณจะแข็งแกร่งอย่างแน่นอน