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 Unauthorized403 Forbidden
ลำดับAuthN ต้องเกิดก่อน AuthZ เสมอ

3 ชั้นของความปลอดภัย

ชั้นทำอะไรถ้าไม่มี
1. Encryption in Transitเข้ารหัสข้อมูลด้วย HTTPS/TLS ก่อนถึง serverส่งรหัสผ่านผ่าน HTTP รหัสถูกดักระหว่างทาง Gatekeeper ไร้ประโยชน์
2. Authenticationยืนยันว่าใครส่ง request — เก็บ hash ไม่ใช่รหัสผ่านฐานข้อมูลรั่วแล้วเห็นรหัสจริงทั้งหมด
3. Authorizationตรวจสิทธิ์จาก token ทุก requestJohn แก้โปรไฟล์ของ Sarah ได้

Encryption vs Hashing — จุดที่พลาดกันบ่อย

Encryption — ย้อนกลับได้ (two-way) "Password123" key "8f2#kQ..." decrypt ด้วย key เดิม → ได้ข้อความเดิมกลับมา Hashing — ย้อนกลับไม่ได้ (one-way) "Password123" xyz789... ย้อนกลับไม่ได้ — ไม่มีทาง "decrypt" hash ตอน login: hash รหัสที่ผู้ใช้พิมพ์ แล้วเทียบ hash กับ hash 1. สมัคร: password → hash → เก็บ xyz789 2. ล็อกอิน: input → hash → เทียบกับ xyz789 ถ้าฐานข้อมูลถูกแฮ็ก เราไม่อยากให้แฮ็กเกอร์เห็น "Password123" จึงเก็บเฉพาะ "ลายนิ้วมือ" ที่ย้อนกลับไม่ได้

เราไม่เคย "ถอดรหัส" รหัสผ่าน เราเทียบลายนิ้วมือเท่านั้น

EncryptionHashing
ทิศทางสองทาง — เข้ารหัสแล้วถอดได้ทางเดียว — ย้อนกลับไม่ได้
ต้องใช้ key ไหมต้องมีไม่ต้อง
ความยาวผลลัพธ์ขึ้นกับข้อมูลต้นทางคงที่เสมอ
ใช้กับข้อมูลที่ต้องอ่านกลับ เช่นข้อมูลระหว่างส่ง (TLS)รหัสผ่าน การตรวจความครบถ้วนของไฟล์
ชนิดย่อยSymmetric (key เดียว เร็ว) / Asymmetric (public + private key ปลอดภัยกว่า)MD5, SHA-256, bcrypt

คุณสมบัติของ Hash 4 ข้อ

  1. One-Way Function — ย้อนกลับหาข้อมูลเดิมไม่ได้
  2. Fixed-Length Output — ไม่ว่า input จะยาวแค่ไหน ผลลัพธ์ยาวเท่ากันเสมอ
  3. Deterministic — input เดิมให้ hash เดิมทุกครั้ง (จึงเทียบกันได้)
  4. Avalanche Effect — เปลี่ยน input นิดเดียว hash เปลี่ยนทั้งหมด

JWT คืออะไร

โครงสร้าง 3 ส่วน

xxxxx . yyyyy . zzzzz Header บอกว่าใช้อัลกอริทึมใด {"alg":"HS256"} Payload ข้อมูลผู้ใช้ที่ไม่อ่อนไหว {"user_id":101,"role":"customer"} Signature ส่วนที่สำคัญที่สุด พิสูจน์ว่า server เป็นคนสร้าง Payload อ่านได้ด้วยตาเปล่า (แค่ Base64) — ห้ามใส่รหัสผ่านหรือเลขบัตร ถ้าผู้ใช้แก้ user_id ในเบราว์เซอร์ signature จะไม่ตรง server จึงปฏิเสธทันที

Header . Payload . Signature — คั่นด้วยจุด 2 ตัว

JWT Lifecycle 5 ขั้น

ขั้นใครทำทำอะไรหมายเหตุด้านความปลอดภัย
1Clientส่ง credentials ด้วย POSTต้องส่งผ่าน HTTPS
2Serverตรวจกับฐานข้อมูลและเทียบ hashbcrypt.compare() — จุดที่เป็น Gatekeeper
3Serverสร้างและเซ็น JWTjwt.sign(payload, secret) — secret อยู่ที่ server เท่านั้น
4Clientเก็บ tokenlocalStorage (เสี่ยง XSS) หรือ cookie แบบ HttpOnly
5Clientแนบ 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" แยกกัน จงวิเคราะห์และเสนอวิธีแก้

  1. ปัญหา — มี 2 จุด: เก็บรหัสผ่านแบบอ่านได้ และข้อความ error เปิดเผยว่าอีเมลมีอยู่จริงหรือไม่
  2. สาเหตุทางเทคนิค — ไม่ได้ใช้ one-way hashing ตอนสมัคร (ควรเป็น bcrypt) และ logic ของ login แยกสาขาตอบต่างกันระหว่าง "ไม่พบผู้ใช้" กับ "รหัสผิด"
  3. ผลกระทบ — ถ้าไฟล์รั่ว ผู้โจมตีได้รหัสผ่านจริงทั้งหมดไปใช้กับเว็บอื่นที่ผู้ใช้ใช้รหัสซ้ำ (credential stuffing) และการตอบ error ต่างกันเปิดทางให้ทำ user enumeration รวบรวมรายชื่อบัญชีที่มีจริง
  4. วิธีแก้เชิงสถาปัตยกรรม — ตอนสมัครให้เก็บ bcrypt.hash(password) เท่านั้น ตอนล็อกอินใช้ bcrypt.compare(), รวมทั้งสองกรณีให้ตอบ 401 พร้อมข้อความเดียวกันว่า "Invalid email or password", บังคับ HTTPS, ออก JWT ที่มีอายุจำกัด และเก็บ secret ไว้ใน environment variable ไม่ hardcode ในโค้ด