ทำไมถึงไม่ควรใช้ JWT ทำ Session
Session คืออะไร
In computer science and networking in particular, a session is a time-delimited two-way link, a practical (relatively high) layer in the TCP/IP protocol enabling interactive expression and information exchange between two or more communication devices or ends
ถ้าจะแปลตรงๆก็ ในทางคอมพิวเตอร์ Session ปกติหมายถึงการเชื่อมต่อแบบส่งทางที่มีการจำกัดเวลาของระหว่างสองปลายทาง(หรือมากกว่า)เพื่อใช้สื่อสารหรือแลกเปลี่ยนข้อมูลกัน
เพราะว่าปกติตัวเว็บที่เราใช้กัน หรือ HTTP เนี่ยจะเป็น stateless protocol
HTTP is called as a stateless protocol because each request is executed independently, without any knowledge of the requests that were executed before it
คือ ตัว HTTP ถูกออกแบบมาให้การเรียกใช้ (request) แต่ละครั้งเป็นแบบอิสระต่อกัน คือจะไม่รู้ว่า คนเข้าใช้งานก่อนหน้า, หรือ request ก่อนหน้านี้เป็นใครทำอะไร ทำให้มันจำไม่ได้ว่าผู้ใช้คนนี้เป็นคนเดิม หรือเป็นคนเดียวกันกับที่ login ไปแล้ว
ซึ่งถ้าเป็นเว็บสำหรับการเผยแพร่ข้อมูลปกติก็ดูจะไม่มีปัญหาอะไร แต่ในการทำเว็บปกติแล้วเราก็มักจะมีการจำกัดการเข้าถึงและแก้ไขข้อมูล (protect resource) ทำให้เราต้องมีระบบล็อกอิน (Login)
ตัวเว็บเอง (HTTP) ก็มีมาตรฐานวิธีการทำล็อกอินที่รองรับมาหลายแบบ แบบที่ใช้ง่ายๆก็จะมี Basic Authentication แต่ไม่แนะนำให้ใช้เพราะข้อมูลที่ต้องเป็บความลับของผู้ใช้เช่น username, password จะถูกส่งไปมาแบบใครๆก็สามารถอ่านเห็นได้เลยไม่ใช่วิธีที่ปลอดภัย
แล้วทีนี้เราจะทำ Session กันด้วยวิธีที่ไหนได้บ้าง? ผมสรุปออกมาเป็นแบบนี้ว่าวิธีที่นิยมใช้ทำ Session กันจริงๆ จะเป็นวิธี Token based ที่ตอนนี้มี 2 แบบหลักๆ
- Server side session - (Opaque) Session Token
- JSON Web Token (JWT)
แต่วิธีที่ผมแนะนำให้ใช้จริงๆ คือ Server side session เพราะการใช้ JWT มาทำ session เนี่ยส่วนตัวผมมองว่าเป็นการใช้งานที่ผิดวัตถุประสงค์ของ JWT ที่เดี๋ยวจะอธิบายในหัวข้อถัดๆได้ว่ามีอะไรบ้าง และทำไม
เพื่อลดความสับสน ผมจะใช้คำว่า Access Token กับ JWT เท่านั้น สำหรับ server side session จะเรียก Access Token ของมันว่่า Opaque Session Token
Server side session คืออะไร
การทำ Session วิธีนี้เวลาที่ผู้ใช้ล็อกอินสำเร็จ, server จะส่งชุดข้อความที่ใช้เป็น Token กลับมาให้ Web Browser เก็บข้อมูลไว้ ซึ่ง Token อาจจะอยู่ในลักษณะของ
- Session ID ที่แกะข้อมูลข้างในไม่ได้ (Opaque)
- หรืออาจจะเป็นในรูปแบบ JWT ที่แกะข้อมูลได้ แต่ไม่ได้ทำงานในลักษณะของ Access Token หรือ Refresh token ของ JWT Session
การใช้งานคือในทุก HTTP Request จะต้องมีการส่ง Token นี้แนบไปให้ Server ด้วย เมื่อ Server ได้รับ จะทำการนำ Token นี้ไป query ดูข้อมูลที่เกี่ยวข้องกับ Session ใน Database เช่น Session นี้หมดอายุแล้วหรือยัง ถูกล็อกเอาท์หรือยัง เป็น Session ของลูกค้าคนไหน เป็นต้น
จริงๆแล้วผมก็ไม่รู้ว่าวิธีนี้มันชื่อว่าอะไร แต่ขอใช้เป็น server side session ละกัน
JWT คืออะไร
หลักการการทำงานพื้นฐานก็จะคล้ายกับ Server side session ที่จะมีการสร้างและส่ง Token มาให้ Web browser เก็บไว้ แล้ว Web browser จะต้องส่ง Token นี้แนบไปกับ Request ด้วยทุกครั้งที่ส่งข้อมูลไปหา Server ซึ่ง Token นี้จะอยู่ในรูปแบบของ JWT
โดย JWT นี้หลักๆแล้วจะมีอยู่ 2 ประเภท
- Access Token ที่จะเป็น JWT เสมอ
- Refresh Token ที่อาจจะเป็น JWT หรืออาจจะเป็น Opaque Token ที่แกะไม่ได้ก็ได้
เรื่องที่ขอข้ามไปก่อน (out of scope)
ในบทความนี้จะเน้นที่เรื่องของการเปรียบเทียบ Server side session กับ JWT เป็นหลักว่าอะไรเหมาะกับการใช้ทำ Session มากกว่ากัน โดยที่จะไม่ได้พูดถึง
- การเก็บ Session ของหน้าบ้าน (web, mobile) ว่าจะเก็บ Session ไว้ที่ไหน - เช่น ที่ cookies, localStorage, sessionStorage
- จะส่ง Session ยังไงให้ปลอดภัย - เช่น ทาง Authorization header, cookies
- การใช้ JWT ใน OAuth และ OIDC
- Server side server โดยละเอียดต้องทำยังไง
- เช่น การป้องกัน CSRF
ทำไมถึงควรใช้วิธี Server side session มากกว่า JWT?
ต่ออายุ Session ได้ง่าย
ถ้าผู้ใช้ยังใช้งานเว็บเราอยู่เรื่อยๆ เขาก็ควรจะต้องใช้ต่อไปได้เรื่อยๆถูกไหม? ผู้ใช้ไม่ควรจะต้องทำการ Login บ่อยๆ
JWT ปกติแล้วจะใช้การส่ง Access Token ไป Server ซึ่ง Access Token มักจะอายุสั้น การที่จะทำให้ Session สามารถต่ออายุได้เรื่อยๆ จะต้องมีการใช้งาน Refresh Token เข้ามาเกี่ยวข้อง เพื่อใช้ในการขอ Access Token ตัวใหม่ เมื่ออันเดิมหมดอายุ (ใน Access Token จะมี exp ระบุเวลาหมดอายุมาแล้วและแก้ไม่ได้) ซึ่งเป็นการเพิ่มความซับซ้อนของระบบ เพราะถ้าใช้ระบบ Server side session ในทุกๆครั้งที่มี Request เข้ามา server สามารถเพิ่มระยะเวลาการหมดอายุของ Session ใน Database ได้เลย โดยไม่ต้องมีการสร้าง Token ตัวมาใหม่มาให้ Web browser ใช้
ข้อเสียสำหรับ server side session จะเป็นเรื่องการรองรับการเข้าใช้งานเยอะๆ ที่ทำให้เกิด database read/write เยอะ แต่ก็มีวิธีจัดการได้อยู่หลายวิธี
ยกเลิกได้ง่าย (revoke/invalidate)
JWT Access Token โดยปกติแล้วจะไม่สามารถยกเลิกหรือถอดสิทธิ์ก่อนที่มันจะถึงเวลาหมดอายุ (exp) ได้เลย เพราะปกติแล้วการตรวจ JWT นั้นที่ server จะใช้แค่การตรวจเวลาหมดอายุและ signature (and alg) ของ JWT เท่านั้นเลยทำให้ไม่มีอะไรที่จะมาใช้ในการตัดสินใจว่า Access Token นี้ถูกยกเลิกไปแล้วได้
ซึ่งถ้าอยากจะทำให้ Access Token สามารถถูกยกเลิกได้นั้นจะต้องใช้การเก็บข้อมูล Access Token ลงใน database และทุกครั้งที่มีการใช้งาน Access Token ก็จะต้องมีการ query ข้อมูล JWT ใน database มาเช็คว่า Access Token ตัวนี้ถูกยกเลิกไปแล้วหรือยัง
ซึ่งถ้าจะต้องเก็บข้อมูล Access Token ใน database และ query ข้อมูลแบบนี้แล้ว จะยังใช้ JWT (AccessToken) อยู่อีกทำไมล่ะ?
รู้ได้ง่ายว่ามีใครทั้งหมดที่ใช้งานอยู่
ประโยชน์ของการรู้ว่ามี Session ไหนที่ยังทำงานหรือใช้งานอยู่บ้าง จะทำให้เราสามารถเลือกยกเลิกหรือล็อกเอาท์ Session บางอันได้ง่าย
การทำงานของ JWT ปกตินั้นจะไม่ได้มีการเก็บข้อมูลว่ามีการขอ Access Token เมื่อไหร่บ้าง ทำให้โดยปกติแล้วจะไม่รู้ว่ามี Session ที่กำลังทำงานหรือถูกใช้งานอยู่จำนวนเท่าไหร่ ระบบที่จะรู้ข้อมูลพวกนี้ได้คือระบบที่มีการใช้งาน Refresh Token ซึ่ง Refresh Token เนี่ยก็มักจะเป็น Token ที่มีการเก็บข้อมูลใน database และทุกครั้งที่มีการใช้งาน Refresh Token ก็จะต้องมีการ query database เสมอเพื่อเช็คว่า Refresh Token นี้ยังใช้งานได้อยู่ไหม หมดอายุ หรือถูกยกเลิก(revoked)ไปแล้วหรือยัง
ซึ่งถ้าจะเป็นแบบนี้แล้ว ทำไมไม่ใช้ server side session ไปเลยล่ะ จะได้ไม่ต้องมีทั้ง Access Token และ Refresh Token แต่มี Token ตัวเดียวก็พอ
มักมีการเก็บข้อมูลที่ค่อนข้างเป็นความลับขอผู้ใช้ใน JWT
จริงๆข้อนี้เป็นรายละเอียดที่หลายๆคนอาจจะข้ามมันไป เพราะเน้นว่าการเก็บข้อมูลลงใน JWT เยอะๆ ทำให้ Web UI มีข้อมูลพร้อมที่จะไปแสดงได้เยอะ ทำงานได้ง่าย สามารถเขียน logic ตัดสินใจเรื่องบางอย่างได้ง่ายขึ้น
ตัวอย่างข้อมูลที่ไม่ควรเก็บใน JWT ที่พบได้บ่อยก็คือ email
เพราะถ้า attacker/hacker มี email ก็จะทำให้ผู้ใช้มีความเสี่ยงต่อ password bruteforce หรือ phishing และการโจมตีหรือหลอกลวงแบบอื่นๆได้สูงขึ้น
รวมไปถึงข้อมูลพวก ชื่อ นามสกุล เบอร์โทร หรือแม้กระทั่ง UserID ด้วย
ความง่ายและสะดวกในการทำระบบด้านความปลอดภัยอื่นๆ
- ล็อกเอาท์ Session อื่น เมื่อมีการล็อกอินใหม่ เพื่อให้มีคนเข้าใช้งานได้แค่ครั้งละ 1 Session หรือ 1 คนแน่ๆ
- การล็อกเอาท์ Session อื่นๆ เมื่อมีการเปลี่ยนรหัสผ่าน
- ความง่ายและสะดวกในการปรับ user permission เพราะหลายๆครั้งจะมีการเก็บ user permission ไว้ใน Access Token ทำให้การปรับ user permission จะไม่ส่งผลทันที ต้องรอการขอ access token ใหม่ หรืออาจจะต้องมีการล็อกเอาท์แล้วล็อกอินใหม่ด้วยซ้ำ
TLDR; สรุปทำไมถึงไม่ควรใช้ JWT มาทำ Session?
- ไม่สามารถทำให้ Token หมดอายุได้ก่อนเวลา
- ระบบหน้าบ้านซับซ้อน เพราะระบบจัดการ Access Token, Refresh Token
- สุดท้ายต้องใช้งาน database เหมือนการทำ server side session อยู่ดี
แล้ว JWT เหมาะกับอะไร?
ส่วนตัวมองว่า JWT นั้นมีประโยชน์แต่ควรจะใช้งานกับสิ่งที่เหมาะ ดังนี้
- ระบบที่จะไม่มีการยกเลิก token ก่อนหมดเวลาแน่ๆ
- ระบบที่ token ทีอายุสั้นมาก เช่น ไม่เกิน 15 นาที
- ระบบที่ไม่มี Refresh Token
- ใช้กับระบบภายในเช่น web server รับ (Opaque) Session Token มาจาก user/web browser, พอ server ทำการเช็คความถูกต้องหมดแล้ว server ก็ทำการดึงข้อมูลมาสร้าง JWT ที่จะเอาไว้ใช้ภายใน เช่น API Gateway สร้าง Access Token ที่มี UserID,Email,Permission,... แล้วส่ง Request ต่อไปที่ Order API, ตัว Order API เมื่อได้รับ request มาก็ใช้การ verify Access Token แบบปกติคือการเช็คเวลาหมดอายุ, JWT Signature แล้วก็แกะข้อมูลผู้ใช้จาก JWT มาใช้ได้เลย โดยไม่ต้องไปขอข้อมูลจาก User API เป็นต้น
References
- Stop using JWT for sessions
- Are you using JWTs for user sessions in the correct way?
- Auth Headers vs JWT vs Sessions — How to Choose the Right Auth Technique for APIs
- Auth0 Access Token
- Auth0 Log users out
- Auth0 Revoke tokens
- JSON Web Tokens (JWT) are Dangerous for User Sessions และอื่นๆ ที่จำลิงค์ไม่ได้