Security, Authentication & Authorization Logic
Identity คือ "Token of Trust" ที่ server ออกให้หลังตรวจสอบอย่างเข้มงวด — ความปลอดภัยไม่ใช่ฟีเจอร์ที่เติมทีหลัง แต่เป็นกระบวนการที่ออกแบบตั้งแต่ต้น
AuthN vs AuthZ — ข้อสอบถามแน่
| Authentication (AuthN) | Authorization (AuthZ) | |
|---|---|---|
| คำถาม | "คุณคือใคร" | "คุณได้รับอนุญาตให้ทำสิ่งนี้ไหม" |
| ตัวอย่าง | การ login | เฉพาะ Admin เท่านั้นที่แก้ราคาสินค้าได้ |
| ใช้อะไรตรวจ | credentials — รหัสผ่าน, OTP, ไบโอเมตริก | policy — เช่น role-based access control (RBAC) |
| HTTP status เมื่อไม่ผ่าน | 401 Unauthorized | 403 Forbidden |
| ลำดับ | AuthN ต้องเกิดก่อน AuthZ เสมอ | |
3 ชั้นของความปลอดภัย
| ชั้น | ทำอะไร | ถ้าไม่มี |
|---|---|---|
| 1. Encryption in Transit | เข้ารหัสข้อมูลด้วย HTTPS/TLS ก่อนถึง server | ส่งรหัสผ่านผ่าน HTTP รหัสถูกดักระหว่างทาง Gatekeeper ไร้ประโยชน์ |
| 2. Authentication | ยืนยันว่าใครส่ง request — เก็บ hash ไม่ใช่รหัสผ่าน | ฐานข้อมูลรั่วแล้วเห็นรหัสจริงทั้งหมด |
| 3. Authorization | ตรวจสิทธิ์จาก token ทุก request | John แก้โปรไฟล์ของ Sarah ได้ |
Encryption vs Hashing — จุดที่พลาดกันบ่อย
เราไม่เคย "ถอดรหัส" รหัสผ่าน เราเทียบลายนิ้วมือเท่านั้น
| Encryption | Hashing | |
|---|---|---|
| ทิศทาง | สองทาง — เข้ารหัสแล้วถอดได้ | ทางเดียว — ย้อนกลับไม่ได้ |
| ต้องใช้ key ไหม | ต้องมี | ไม่ต้อง |
| ความยาวผลลัพธ์ | ขึ้นกับข้อมูลต้นทาง | คงที่เสมอ |
| ใช้กับ | ข้อมูลที่ต้องอ่านกลับ เช่นข้อมูลระหว่างส่ง (TLS) | รหัสผ่าน การตรวจความครบถ้วนของไฟล์ |
| ชนิดย่อย | Symmetric (key เดียว เร็ว) / Asymmetric (public + private key ปลอดภัยกว่า) | MD5, SHA-256, bcrypt |
คุณสมบัติของ Hash 4 ข้อ
- One-Way Function — ย้อนกลับหาข้อมูลเดิมไม่ได้
- Fixed-Length Output — ไม่ว่า input จะยาวแค่ไหน ผลลัพธ์ยาวเท่ากันเสมอ
- Deterministic — input เดิมให้ hash เดิมทุกครั้ง (จึงเทียบกันได้)
- Avalanche Effect — เปลี่ยน input นิดเดียว hash เปลี่ยนทั้งหมด
JWT คืออะไร
- JSON Web Token — มาตรฐานเปิด (RFC 7519) สำหรับส่งข้อมูลระหว่างฝ่ายอย่างปลอดภัยในรูป JSON object ที่กระชับและใช้ใน URL ได้
- เป็นคำตอบของปัญหา stateless backend — server ไม่ต้องจำ session
- Self-Contained — token บรรจุข้อมูลผู้ใช้ที่จำเป็นไว้ในตัว ลดการ query ฐานข้อมูลซ้ำ ๆ
- Compact — ส่งผ่าน HTTP header, URL หรือ POST parameter ได้
โครงสร้าง 3 ส่วน
Header . Payload . Signature — คั่นด้วยจุด 2 ตัว
JWT Lifecycle 5 ขั้น
| ขั้น | ใครทำ | ทำอะไร | หมายเหตุด้านความปลอดภัย |
|---|---|---|---|
| 1 | Client | ส่ง credentials ด้วย POST | ต้องส่งผ่าน HTTPS |
| 2 | Server | ตรวจกับฐานข้อมูลและเทียบ hash | bcrypt.compare() — จุดที่เป็น Gatekeeper |
| 3 | Server | สร้างและเซ็น JWT | jwt.sign(payload, secret) — secret อยู่ที่ server เท่านั้น |
| 4 | Client | เก็บ token | localStorage (เสี่ยง XSS) หรือ cookie แบบ HttpOnly |
| 5 | Client | แนบ token ทุก request ถัดไป | Authorization: Bearer <token> |
// ฝั่ง server — ออก token const token = jwt.sign({ id: user.id, role: user.role }, process.env.SECRET, { expiresIn: '1h' }); res.status(200).json({ token }); // ฝั่ง server — ตรวจ token ใน middleware function verifyToken(req, res, next) { const header = req.headers['authorization']; if (!header) return res.status(401).json({ error: 'No token' }); const token = header.split(' ')[1]; // ตัดคำว่า Bearer ออก try { req.user = jwt.verify(token, process.env.SECRET); next(); } catch (err) { return res.status(401).json({ error: 'Invalid token' }); } }
Bearer header
GET /api/profile HTTP/1.1 Host: myshop.example.com Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAxfQ.4f3s...
Bearer เว้นวรรค แล้วตามด้วย token — ฝั่ง server จึงต้อง split(' ')[1]User Enumeration
คำถามจากสไลด์: ทำไม error ต้องเขียนว่า "Invalid email or password" แทนที่จะเป็น "Email not found"
คำตอบ: ถ้าระบบตอบต่างกันระหว่าง "ไม่มีอีเมลนี้" กับ "รหัสผิด" ผู้โจมตีจะไล่เดาอีเมลเพื่อหาว่าบัญชีไหนมีอยู่จริง (user enumeration) แล้วเอารายชื่อนั้นไปโจมตีต่อด้วยการเดารหัสผ่านหรือ phishing
ทั้งสองกรณีจึงต้องตอบ 401 พร้อมข้อความเดียวกัน และควรใช้เวลาตอบใกล้เคียงกันด้วย
เก็บ token ที่ไหนดี
| ที่เก็บ | ข้อดี | ความเสี่ยง |
|---|---|---|
| localStorage | ง่าย อยู่ข้ามการ refresh และทุกแท็บ | XSS อ่านได้ — JS ทุกตัวบนหน้าเข้าถึงได้ |
| sessionStorage | หายเมื่อปิดแท็บ ลดความเสี่ยงลงบ้าง | ยังโดน XSS อ่านได้เหมือนกัน |
| Cookie (HttpOnly, Secure, SameSite) | JS อ่านไม่ได้ จึงกัน XSS ได้ | ต้องระวัง CSRF จึงต้องตั้ง SameSite |
แนวข้อสอบอัตนัย
โจทย์: ระบบเก็บรหัสผ่านเป็น plaintext ในไฟล์ auth_user.json และตอนล็อกอินล้มเหลวตอบว่า
"Email not found" หรือ "Wrong password" แยกกัน จงวิเคราะห์และเสนอวิธีแก้
- ปัญหา — มี 2 จุด: เก็บรหัสผ่านแบบอ่านได้ และข้อความ error เปิดเผยว่าอีเมลมีอยู่จริงหรือไม่
- สาเหตุทางเทคนิค — ไม่ได้ใช้ one-way hashing ตอนสมัคร (ควรเป็น bcrypt) และ logic ของ login แยกสาขาตอบต่างกันระหว่าง "ไม่พบผู้ใช้" กับ "รหัสผิด"
- ผลกระทบ — ถ้าไฟล์รั่ว ผู้โจมตีได้รหัสผ่านจริงทั้งหมดไปใช้กับเว็บอื่นที่ผู้ใช้ใช้รหัสซ้ำ (credential stuffing) และการตอบ error ต่างกันเปิดทางให้ทำ user enumeration รวบรวมรายชื่อบัญชีที่มีจริง
- วิธีแก้เชิงสถาปัตยกรรม — ตอนสมัครให้เก็บ
bcrypt.hash(password)เท่านั้น ตอนล็อกอินใช้bcrypt.compare(), รวมทั้งสองกรณีให้ตอบ 401 พร้อมข้อความเดียวกันว่า "Invalid email or password", บังคับ HTTPS, ออก JWT ที่มีอายุจำกัด และเก็บ secret ไว้ใน environment variable ไม่ hardcode ในโค้ด