BlogBackend & Databaseการจัดการสิทธิ์ผู้ใช้ในแอปพลิเคชัน ด้วย RBAC
ออกแบบระบบสิทธิ์ผู้ใช้ผ่านตัวอย่างแอปจัดการใบสั่งซื้อ เรียนรู้การแยก Role กับ Permission ตรวจเจ้าของข้อมูลและบริษัท พร้อมโค้ด Python และกรณีทดสอบก่อนใช้งาน
Kittipong Chankhotr
ทีมงาน KittiStack
October 28, 2026·7 min read
การจัดการสิทธิ์ผู้ใช้ในแอปพลิเคชัน ด้วย RBAC
การจัดการสิทธิ์ผู้ใช้ในแอปพลิเคชัน คือการกำหนดว่าผู้ใช้คนไหนทำอะไรกับข้อมูลใดได้ เช่น พนักงานแก้ใบสั่งซื้อของตัวเองได้ แต่การอนุมัติต้องเป็นหน้าที่ของผู้จัดการ ระบบจึงต้องตรวจทั้งสิทธิ์การทำงานและขอบเขตข้อมูล ไม่ใช่ตรวจเพียงว่าล็อกอินแล้วหรือยัง
บทความนี้ใช้ตัวอย่างแอปจัดการใบสั่งซื้อ เพื่ออธิบาย Role, Permission และการตรวจสิทธิ์ใน Backend พร้อมโค้ด Python และแนวทางเชื่อมกับ Supabase RLS
การจัดการสิทธิ์ผู้ใช้ในแอปพลิเคชันต่างจากการล็อกอินอย่างไร
ระบบล็อกอินกับระบบสิทธิ์ทำงานเกี่ยวข้องกัน แต่ตอบคนละคำถาม การแยกหน้าที่ให้ชัดช่วยให้เราไม่เผลออนุญาตทุกอย่างแก่ผู้ใช้ที่ล็อกอินสำเร็จ
#Python#Supabase#Authorization#RBAC#API Security
Authentication
Authorization | ผู้เรียกทำสิ่งนี้กับข้อมูลนี้ได้หรือไม่ | A แก้ใบสั่งซื้อรายการนี้ได้หรือไม่ |
พนักงานสองคนล็อกอินได้เหมือนกัน แต่ไม่ได้แปลว่าควรเปิดเอกสารส่วนตัวของกันและกันได้ เช่นเดียวกับผู้จัดการที่อาจอนุมัติรายการของบริษัทตัวเองได้ แต่ไม่มีสิทธิ์อนุมัติให้บริษัทอื่น
การซ่อนปุ่มช่วยให้หน้าจอเหมาะกับผู้ใช้ แต่ API ต้องตรวจสิทธิ์เองทุกคำขอ เพราะผู้ใช้สามารถส่งคำขอโดยตรงได้แม้หน้าจอไม่มีปุ่มนั้น
Authorization Cheat Sheet ของ OWASP แนะนำให้ให้สิทธิ์เท่าที่จำเป็น ปฏิเสธเป็นค่าเริ่มต้น และตรวจสิทธิ์ทุกคำขอ หลักเหล่านี้ควรใช้กับทั้ง API และเส้นทางเข้าถึงทรัพยากรอื่นของระบบ
บทบาทกำหนดกลุ่มการทำงาน ส่วนขอบเขตข้อมูลกำหนดว่าทำงานกับข้อมูลของใครได้
Role กับ Permission ควรแยกกันอย่างไร
Role คือกลุ่มหน้าที่ เช่น staff, manager และ admin ส่วน Permission คือการกระทำที่อนุญาต เช่น orders:read, orders:update และ orders:approve
RBAC หรือ Role-Based Access Control ผูกสิทธิ์ไว้กับบทบาท แล้วมอบบทบาทให้ผู้ใช้ เมื่อปรับสิทธิ์ของบทบาท ผู้ใช้ที่ได้รับบทบาทนั้นก็ใช้กฎชุดเดียวกัน ช่วยให้ตรวจทานง่ายกว่าการกำหนดสิทธิ์เฉพาะคนจำนวนมาก
ตามแนวคิดใน เอกสาร RBAC ของ Auth0 บทบาทหนึ่งประกอบด้วยหลาย Permission และผู้ใช้หนึ่งคนอาจมีหลายบทบาทได้ อย่างไรก็ตาม ทีมต้องกำหนดให้ชัดว่าสิทธิ์แต่ละรายการครอบคลุมข้อมูลแค่ไหน
ตัวอย่างเช่น orders:update บอกว่ามีสิทธิ์แก้ใบสั่งซื้อ แต่ยังไม่ตอบว่าแก้ได้ทุกใบหรือเฉพาะของตัวเอง จึงต้องมีเงื่อนไขเจ้าของข้อมูล บริษัท และสถานะเอกสารประกอบด้วย
ออกแบบตารางสิทธิ์ก่อนเขียนโค้ด
เริ่มจากลิสต์งานที่ระบบทำได้ แล้วให้เจ้าของกระบวนการช่วยระบุว่าใครควรทำงานใด ตารางด้านล่างเป็นข้อกำหนดสมมติสำหรับอธิบาย ไม่ใช่มาตรฐานที่ทุกธุรกิจต้องใช้
ในตัวอย่างนี้ Admin ดูแลสมาชิก จึงไม่ได้รับสิทธิ์อ่านเอกสารธุรกิจโดยอัตโนมัติ หากบุคคลเดียวทำหลายหน้าที่ สามารถมอบหลายบทบาทตามงานจริงได้ โดยยังต้องรักษาขอบเขตบริษัท
กฎอนุญาตที่นำไปเขียนโค้ดจึงควรระบุให้ครบ เช่น “ผู้ใช้ต้องมี orders:update เป็นสมาชิกที่ยังใช้งานอยู่ของบริษัทเจ้าของเอกสาร เป็นผู้สร้างรายการ และเอกสารยังเป็น draft” ประโยคนี้ชัดกว่าการเขียนว่า “Staff แก้เอกสารได้”
RBAC, ABAC และ ReBAC เลือกใช้แบบไหน
เมื่อกฎเริ่มละเอียดขึ้น การสร้างบทบาทใหม่สำหรับทุกเงื่อนไขอาจทำให้ดูแลยาก เราสามารถใช้โมเดลการควบคุมสิทธิ์หลายแบบร่วมกันได้
สำหรับระบบตัวอย่าง เราใช้ RBAC เพื่อระบุประเภทงาน แล้วตรวจบริษัท เจ้าของ และสถานะเพิ่มเติม เมื่อระบบมีการแชร์เอกสารเป็นรายคนหรือมีโครงสร้างทีมหลายชั้น ค่อยพิจารณาโมเดลความสัมพันธ์ที่รองรับกฎเหล่านั้น
ตัวอย่าง Python ตรวจ Permission และเจ้าของข้อมูล
โค้ดนี้แสดงฟังก์ชันตัดสินสิทธิ์แก้ใบสั่งซื้อ โดยสมมติว่า Backend ยืนยันตัวตนแล้ว และโหลดข้อมูลสมาชิกบริษัทกับ Permission จากแหล่งที่เชื่อถือได้ ตัวอย่างไม่ได้รวมระบบล็อกอินหรือการเชื่อมต่อฐานข้อมูล
from dataclasses import dataclass
@dataclass(frozen=True)
class Membership:
user_id: str
tenant_id: str
permissions: frozenset[str]
active: bool
@dataclass(frozen=True)
class Order:
id: str
tenant_id: str
owner_id: str
status: str
def can_update_order(
user_id: str,
membership: Membership,
order: Order,
) -> bool:
if not membership.active:
return False
if membership.user_id != user_id:
return False
if membership.tenant_id != order.tenant_id:
return False
if "orders:update" not in membership.permissions:
return False
if order.owner_id != user_id:
return False
return order.status == "draft"
ฟังก์ชันนี้อนุญาตเมื่อเงื่อนไขครบทุกข้อ หากบทบาทไม่รู้จักหรือไม่มี Permission ที่ต้องใช้ ให้ปฏิเสธแทนการให้สิทธิ์กว้างไว้ก่อน เมื่อเรียกใช้งานจริง ต้องนำผลลัพธ์ไปบังคับก่อนเขียนข้อมูลด้วย ไม่ใช่เรียกฟังก์ชันแล้วปล่อยให้คำสั่งบันทึกทำงานต่อเสมอ
ห้ามนำ user_id, role, permissions หรือ tenant_id จาก request body มาใช้เป็นข้อเท็จจริงโดยไม่ตรวจสอบ รหัสบริษัทที่ client เลือกใช้เป็นอินพุตสำหรับค้นหาสมาชิกได้ แต่ต้องตรวจว่าผู้ใช้เป็นสมาชิกบริษัทนั้นจริง
ตอนบันทึกควรอัปเดตเฉพาะฟิลด์ที่อนุญาต เช่น รายละเอียดสินค้า ไม่รับค่าทั้งก้อนไปเขียนทับ owner_id หรือสถานะอนุมัติ และต้องป้องกันกรณีสถานะเอกสารเปลี่ยนระหว่างตรวจสิทธิ์กับบันทึก เช่น ใช้ transaction ร่วมกับการล็อกแถว หรือคำสั่งอัปเดตที่ตรวจเงื่อนไขซ้ำอย่างเหมาะสม
ถ้าใช้ Supabase ต้องตรวจสิทธิ์ตรงไหน
ใน Supabase เราสามารถแบ่งหน้าที่เป็น Supabase Auth สำหรับยืนยันตัวตน, GRANT สำหรับสิทธิ์ระดับตาราง และ RLS สำหรับจำกัดแถวข้อมูล หากยังไม่คุ้นกับบริการนี้ อ่านพื้นฐานได้ที่ Supabase คืออะไรและเหมาะกับงานแบบไหน
RLS เป็นจุดบังคับสิทธิ์ที่ระดับฐานข้อมูล ส่วน RBAC เป็นวิธีจัดกลุ่มสิทธิ์ ทั้งสองอย่างจึงใช้ร่วมกันได้ เช่น Policy ตรวจสมาชิกบริษัทและสิทธิ์ของผู้เรียก ก่อนอนุญาตให้อ่านแถวข้อมูล
หาก Backend ใช้กุญแจสิทธิ์สูงที่ข้าม RLS ต้องตรวจสิทธิ์ผู้ใช้ในเส้นทางนั้นเองด้วย การเปิด RLS ไม่ได้ทำให้คำสั่งจากทุกบทบาทถูกจำกัดเหมือนกัน และกฎใน Backend กับฐานข้อมูลควรให้ผลสอดคล้องกัน
ข้อผิดพลาดที่ควรตรวจตั้งแต่แรก
ตรวจสิทธิ์เฉพาะหน้าแสดงรายละเอียด
แม้หน้าเอกสารรายใบจะตรวจสิทธิ์ถูกต้อง ข้อมูลยังอาจหลุดจากหน้ารายการ การค้นหา ยอดรวม ดาวน์โหลดไฟล์ หรือส่งออก CSV ได้ ทุกเส้นทางต้องใช้ขอบเขตสิทธิ์เดียวกัน รวมถึงงานเบื้องหลังที่สร้างรายงานให้ผู้ใช้
ใช้ UUID แทนการตรวจสิทธิ์
รหัสที่เดายากช่วยลดการไล่หมายเลข แต่ไม่ได้ยืนยันว่าผู้เรียกมีสิทธิ์ในรายการนั้น หากผู้ใช้ได้รหัสเอกสารของคนอื่นมา API ยังต้องปฏิเสธตามกฎเดิม
OWASP อธิบายปัญหาการเปลี่ยนรหัสรายการเพื่อเข้าถึงข้อมูลคนอื่นไว้ในหัวข้อ Broken Object Level Authorization การตรวจว่าเข้าถึง endpoint ได้จึงต้องตามด้วยการตรวจสิทธิ์ต่อข้อมูลเป้าหมายด้วย
เปิดให้ผู้ใช้แก้ข้อมูลสิทธิ์ของตัวเอง
Endpoint แก้โปรไฟล์ทั่วไปไม่ควรยอมให้เปลี่ยน Role, Permission หรือบริษัทด้วยการเพิ่มฟิลด์ใน JSON ควรแยกรายการฟิลด์ที่รับได้ และตรวจสิทธิ์การจัดการสมาชิกในเส้นทางเฉพาะ
ถอนสิทธิ์แล้วแต่ Token หรือ Cache ยังใช้ค่าเดิม
ถ้าเก็บบทบาทไว้ใน token ที่ออกไปแล้ว การแก้บทบาทในฐานข้อมูลไม่ได้ทำให้ claims ภายใน token เดิมเปลี่ยนทันที ต้องกำหนดว่าการถอนสิทธิ์ควรมีผลภายในเวลาเท่าใด แล้วเลือกรูปแบบตรวจสิทธิ์ อายุ token และการล้าง cache ให้รองรับข้อกำหนดนั้น
ควรบันทึกเหตุการณ์สำคัญ เช่น การเปลี่ยนบทบาทและการอนุมัติเอกสาร โดยระบุผู้กระทำ บริษัท รายการ และเวลา เพื่อให้ตรวจย้อนหลังได้ หลีกเลี่ยงการบันทึก access token หรือข้อมูลลับลง log
เช็กลิสต์ทดสอบสิทธิ์ก่อนเปิดใช้งาน
ทดสอบทั้งกรณีที่ควรอนุญาตและควรปฏิเสธ ตารางนี้ใช้ตรวจระบบตามกฎสมมติในบทความ และสามารถปรับเป็น automated tests ของ API ได้
ตรวจข้อมูลหลังคำขอด้วย ไม่ดูเฉพาะสถานะ HTTP และทดสอบด้วยบัญชีหลายบทบาทจริง หากผู้ใช้สังกัดหลายบริษัท ต้องเพิ่มกรณีสลับบริษัทเพื่อยืนยันว่าสิทธิ์จากบริษัทหนึ่งไม่ติดไปใช้กับอีกบริษัท
คำถามที่พบบ่อย
แอปเล็กจำเป็นต้องทำระบบสิทธิ์ไหม
ถ้ามีข้อมูลที่ผู้ใช้ไม่ควรเห็นร่วมกัน ต้องกำหนดขอบเขตตั้งแต่แรก เริ่มจากกฎเจ้าของข้อมูลและ Permission ไม่กี่รายการได้ โดยยังไม่จำเป็นต้องมีหน้าจอจัดการบทบาทที่ซับซ้อน
ใช้ JWT แล้วต้องตรวจสิทธิ์อีกไหม
ต้องตรวจ JWT ที่ผ่านการตรวจสอบช่วยยืนยันตัวตนและ claims แต่ระบบยังต้องตัดสินว่าผู้ใช้ทำคำสั่งนี้กับข้อมูลรายการนี้ได้หรือไม่ รวมถึงพิจารณาว่าสิทธิ์ที่อ้างอิงยังเป็นปัจจุบันเพียงพอหรือไม่
ผู้ใช้หนึ่งคนมีหลาย Role ได้ไหม
ได้ แต่ต้องกำหนดวิธีรวมสิทธิ์และขอบเขตของบทบาทให้ชัด เช่น คนเดียวเป็น Manager ในบริษัท A แต่เป็น Staff ในบริษัท B การตรวจสิทธิ์ต้องใช้บทบาทของบริษัทที่กำลังเข้าถึง
ควรเก็บ Role ใน Frontend หรือ Backend
Frontend เก็บไว้ปรับหน้าจอได้ ส่วนการบังคับสิทธิ์ต้องอาศัยข้อมูลที่ตรวจสอบได้ใน Backend หรือฐานข้อมูล ค่าที่แก้ไขได้จากเบราว์เซอร์ไม่ควรเป็นแหล่งตัดสินสิทธิ์เพียงแหล่งเดียว
เริ่มจากกฎที่ตรวจสอบได้
เลือกหนึ่งงาน เช่น การแก้ใบสั่งซื้อ เขียนให้ชัดว่าใครทำได้กับข้อมูลไหนและสถานะใด แล้วทำจุดตรวจสิทธิ์ที่ใช้ร่วมกันพร้อมชุดทดสอบ ก่อนขยายไปยังงานอื่น วิธีนี้ช่วยให้ทีมเห็นข้อกำหนดที่ยังคลุมเครือก่อนกลายเป็นเงื่อนไขกระจัดกระจายในโค้ด
การจัดการสิทธิ์ผู้ใช้ในแอปพลิเคชัน ด้วย RBAC | KittiStack