รู้จักรูปแบบ Authentication ระหว่าง Frontend และ Backend
/ 8 min read
สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video
เนื้อหานี้ทำมาเนื่องจากใน Web development 101 ที่ผมทำไปก่อนหน้านี้ ผมยังไม่ได้นำเสนอวิธีการทำ Authenticaion ผ่าน Backend เลย
Session นี้เลยจะพามาทำ login และการทำเรื่องกั้นสิทธิ์ว่าสามารถทำอย่างไรได้บ้าง
เราจะแบ่งหัวข้อออกจากกันตามนี้
เราจะมาตอบทั้ง 4 คำถามนี้ใน Session นี้กัน
- เราจะเก็บ password ไว้ในฐานข้อมูลอย่างไร ?
- เราจะยืนยันได้อย่างไรว่า เราที่เข้ามาคือใคร ?
- เราจะทำ login ได้ยังไง ? และทำ login แล้วได้อะไรออกมาเป็นเครื่องยืนยันตัวตน ?
- เราจะป้องกันไม่ให้คนทั่วไปเข้ามายิง API ที่เราไม่อยากให้คนทั่วไปยิงได้ยังไง ?
z
- สำหรับ project นี้เราจะ setup docker 2 ตัวคือ mysql และ phpmyadmin เท่านั้น
- โดยโจทย์ของ mysql คือเราจะสร้าง table ชื่อ users ขึ้นมาและเราจะทำการเก็บข้อมูล email, password เอาไว่้เพื่อทำการ login
docker-compose.yml
Structure project จะเป็นตามนี้
library ที่ใช้ในรอบนี้
express
สำหรับ library node สำหรับทำ Rest APIcors
สำหรับการเปิดให้ฝั่ง Frontend สามารถยิงเข้ามาผ่าน cross domain ได้mysql2
สำหรับจัดการฐานข้อมูล mysqljsonwebtoken
สำหรับการเข้ารหัสข้อมูลสำหรับแนบเข้า token ตอน login สำเร็จcookie-parser
สำหรับเรียกใช้และ save cookiebcrypt
สำหรับเข้ารหัส passwordexpress-session
สำหรับการ login ในเคสที่ใช้ session
โดย index.js
เราจะเพิ่ม code ไว้ตามนี้เพื่อ config เริ่มต้นก่อน
โจทย์ของหัวข้อนี้เราจะทำ API อะไรกันบ้าง
เราจะทำ API
- POST
/api/register
สำหรับสมัครสมาชิก email, password เข้ามา (จะใช้ในข้อ 1) - POST
/api/login
สำหรับ login สมาชิกด้วย email, password เพื่อยืนยันตัวตนและเข้าสู่ระบบ (จะใช้ในข้อ 2,3) - GET
/api/users
สำหรับ list users ทั้งหมดในระบบโดยจะอนุญาตเฉพาะคนที่เป็น role admin เท่านั้น (จะใช้ในข้อที่ 4)
1. เราจะเก็บ password ไว้ในฐานข้อมูลอย่างไร ?
- ใช้ bcrypt ซึ่งเป็น password hashing function ที่สร้างขึ้นจากพื้นฐานของ Blowfish cipher เป็นการเข้ารหัสแบบทางเดียว
- ปกติการเข้ารหัสมี 2 แบบ (แบ่งแบบง่ายๆนะ) คือ เข้ารหัสแบบ decrypt ได้ (แกะข้อมูลเข้ารหัสออกมาได้) และ เข้ารหัสแบบทางเดียว (เช่น hashing function)
2. เราจะยืนยันได้อย่างไรว่า เราที่เข้ามาคือใคร ?
3. เราจะทำ login ได้ยังไง ? และทำ login แล้วได้อะไรออกมาเป็นเครื่องยืนยันตัวตน ?
1. gen token ส่งให้ Frontend เก็บไว้
- JWT Token คือ token เข้ารหัสมาตรฐาน RFC 7519 ที่สามารถนำมา decode ข้อมูลกลับได้
- มีความปลอดภัยในแง่การส่งข้อมูลไปมาระหว่าง Frontend, Backend แต่ ไม่เหมาะสำหรับเก็บข้อมูลที่มีความปลอดภัยสูง
2. ใส่ผ่าน cookie เข้า domain Frontend
3. ใช้ผ่าน session (ไม่ต้องผ่าน client)
- session คือ value ที่จะถูกสร้างขึ้นมาเมื่อ Client มีการเปิดเว็บบราวเซอร์และติดต่อกับ URL ของเว็บไซต์นั้น (และจะถูกทำลายลงเมื่อผู้ใช้ได้ทำการปิด Browser หรือ Client)
4. เราจะป้องกันไม่ให้คนทั่วไปเข้ามายิง API ที่เราไม่อยากให้คนทั่วไปยิงได้ยังไง ?
เราจะเพิ่ม GET /api/users
กับ funtion middleware authenticateToken
เข้ามา
โดยเราจะจับคู่กับหัวข้อที่ 3 ในเรื่องของการ login คือ
- ถ้าใช้ วิธีที่ 1 เก็บผ่าน web storage = ต้องให้ client ส่ง token มาผ่าน Bearer เข้ามา เพื่อทำการเช็คว่า token ถูกต้องหรือไม่ (เป็นวิธีที่ 1 ของหัวข้อนี้)
- ถ้าใช้ วิธีทีี่ 2 เก็บผ่าน Cookie = server จะอ่าน cookie (ที่แนบมาคู่กับ HTTP request) เพื่อทำการเช็คว่า token ถูกต้องหรือไม่ (เป็นวิธีที่ 2 ของหัวข้อนี้)
- ถ้าใช้ วิธีทีี่ 3 เก็บผ่าน Session = server จะอ่านค่าจาก session (ใน server เอง) เช็คว่า มีการเก็บตัวแปรผ่าน request user มาไหม ? (เป็นวิธีที่ 3 ของหัวข้อนี้)
1. ผ่าน header Authorization: Bearer <token>
1. gen token ส่งให้ Frontend เก็บไว้
2. ผ่าน cookie
3. เช็คผ่านค่าใน session
ข้อดี / ข้อเสียของแต่ละวิธี
วิธีที่ 1 ใช้ web storage เก็บ token
ข้อดี
- implement ง่าย
- สามารถเก็บข้อมูลขนาดเท่าไหร่ก็ได้ ไม่จำกัด เนื่องจาก Web storage ไม่ได้มี limit เอาไว้
- access ผ่าน client ได้ง่าย
- data ก็จัดการง่ายเนื่องจาก DB บน Browser เป็น key - value อยู่แล้ว
ข้อสังเกต
- สามารถโดน Cross-site Scripting ได้ หาก token หลุดไป
- Data ถูกเก็บเป็น plain text
- ไม่มี built-in expiry ในตัว data (แต่ปัญหานี้แก้จากฝั่ง Backend ได้)
- เก็บ sensitive data ไม่ได้เลย เพราะจะถูกแกะมาอ่านได้ (แม้จะเป็น jwt ก็แกะมาได้)
วิธีที่ 2 ใช้ cookie เก็บ token
ข้อดี
- cookie เมื่อเปิดใช้งาน นี้จะถูกส่งผ่าน http request เสมอ สามารถตรวจสอบทุกๆ request ที่ต้องการเช็ค cookie ได้ง่าย (ไม่ต้องคอยระมัดระวังว่าจะลืมส่ง header ไหม)
- มี built-in expiry
- สามารถเพิ่มความ secure ผ่าน flag “HttpOnly”, “Secure” ได้ (คือ client อยู่ๆมาเรียกใช้ตรงๆไม่ได้)
ข้อสังเกต
- ขนาดจำกัด (4KB per Cookie) แตกต่างกับ Web storage
- การส่ง cookie ไปในทุก request อาจจะเพิ่ม load server (นิดนึง)
- เจอปัญหาการโจมตีด้วยวิธี Cross-Site Request Forgery (CSRF) = การอ่าน cookie ผ่าน site อื่น โดยจำลองทำเหมือนเว็บไซต์หลักกำลังส่งข้อมูลเข้าไป (ปกติแก้ได้โดยการ check site ที่มาให้แน่ใจก่อนดำเนินการ)
- สามารถโดน block หรือ ลบโดย user บน Browser ได้ (กรณีที่ user block cookie ซึ่งสมัยนี้คิดว่าไม่น่ามีแล้ว)
วิธีที่ 3 ใช้ session
ข้อดี
- data ทั้งหมดจะอยู่บน server เท่านั้น ความปลอดภัยจะมากกว่า 2 วิธีที่ผ่านมา
- สามารถเก็บข้อมูลขนาดใหญ่เท่าไหร่ก็ได้ (ตาม storage server)
- data จะไม่ส่งระหว่าง http request (เนื่องจากมันเป็นการเก็บแค่บน server) = ประหยัด load server ได้
- สามารถเก็บในรูปแบบไหนก็ได้ (in-memory, file, database)
ข้อสังเกต
- ต้องเพิ่มส่วนของ data management ส่วน session มา (เนื่องจาก server ต้องเป็นคนจัดการเอง)
- ถ้า server crash แล้วไม่ได้ handle เรื่อง data ใน session ไว้ = session จะหายไป ทำให้ login ทั้งระบบหลุดได้ (session จะเปลี่ยนไปตอน server กลับมาใหม่)
- ต้องเพิ่ม mechanism ส่วนของการ manage expire ใน session (เพื่อไม่ให้ login ค้างอยู่ตลอดเวลา)
- ถ้าเกิด user ในระบบเยอะ = data session ก็จะเยอะตาม ต้องนึกถึงการ scale ระบบนี้เพิ่มเติมมา
Github
https://github.com/mikelopster/auth-express-example
Reference อื่นๆ
- https://dev.to/honeybadger/complete-guide-to-authentication-in-javascript-3576 (ในบทความนี้มี refreshtoken)