BotKit
บทความ › เขียนโค้ด & ดูแลระบบ

Supabase RLS เบื้องต้น: anon key ไม่ได้ปลอดภัยเอง ถ้าไม่เปิด Row Level Security

Supabase ให้ key มาสองแบบ และหลายคนเอา key ที่ใช้ในเว็บ (anon key) ใส่ในหน้าเว็บแล้วคิดว่าปลอดภัย เพราะ "เป็นแค่ key สาธารณะ" สิ่งที่ทำให้ปลอดภัยจริงๆ ไม่ใช่ตัว key แต่คือ Row Level Security (RLS) ถ้าไม่ได้เปิด ใครที่เปิดซอร์สหน้าเว็บเราได้ ก็อ่านและแก้ข้อมูลทั้งตารางได้

key สองแบบ ใช้ต่างกัน

keyใส่ที่ไหนได้สิทธิ์
anon / publishableหน้าเว็บ แอปมือถือ (คนเห็นได้)ถูกจำกัดด้วย RLS
service_role / secretเซิร์ฟเวอร์เท่านั้นข้าม RLS ทั้งหมด ทำได้ทุกอย่าง
⚠️ ห้ามใส่ service_role key ในแอปมือถือหรือหน้าเว็บเด็ดขาด แอปมือถือถูกแกะไฟล์ได้ ใครได้ key นี้ไปเท่ากับได้สิทธิ์แอดมินทั้งฐานข้อมูล

เปิด RLS แล้วเกิดอะไรขึ้น

alter table bookings enable row level security;

พอเปิดแล้วยังไม่มี policy คำขอที่ใช้ anon key จะอ่าน/เขียนตารางนั้นไม่ได้เลย (ได้ผลว่าง ไม่ใช่ error) ส่วนเซิร์ฟเวอร์ที่ใช้ service_role ยังทำงานได้ปกติ

นี่คือค่าที่ผมใช้กับตารางภายในเกือบทุกตัว เช่น ตารางจำงานที่แจ้งเตือนไปแล้ว ตารางสถิติคนเข้าเว็บ หรือตารางเทรดทดลอง: เปิด RLS และไม่ให้ policy กับ anon เลย ให้เซิร์ฟเวอร์เป็นคนเดียวที่แตะข้อมูล

เมื่อไหร่ต้องเขียน policy

เมื่อหน้าเว็บหรือแอปต้องคุยกับฐานข้อมูลตรงๆ ตัวอย่างจากแอปท่องเที่ยวที่ให้คนทั่วไปอ่านรายการสถานที่ได้ แต่แก้ไม่ได้

alter table places enable row level security;

-- ทุกคนอ่านได้ เฉพาะรายการที่เผยแพร่แล้ว
create policy "public read published places"
  on places for select
  to anon, authenticated
  using (is_published = true);

ถ้าให้ผู้ใช้ที่ล็อกอินเพิ่มข้อมูลของตัวเองได้ ใช้ auth.uid() ผูกกับคอลัมน์เจ้าของ

create policy "users insert own reviews"
  on reviews for insert
  to authenticated
  with check (user_id = auth.uid());

create policy "users update own reviews"
  on reviews for update
  to authenticated
  using (user_id = auth.uid())
  with check (user_id = auth.uid());

using ตรวจแถวที่มีอยู่ (แก้ได้เฉพาะแถวของตัวเอง) ส่วน with check ตรวจค่าใหม่ (เปลี่ยน user_id เป็นของคนอื่นไม่ได้) ตอน update ควรมีทั้งสองอย่าง

ทดสอบว่าปิดจริง

อย่าทดสอบใน SQL Editor ของหน้า dashboard เพราะรันด้วยสิทธิ์สูง จะเห็นข้อมูลทุกแถวเสมอ ให้ยิง REST API ด้วย anon key ตรงๆ แบบที่คนนอกทำได้

curl "https://<project>.supabase.co/rest/v1/bookings?select=*" \
  -H "apikey: <ANON_KEY>" -H "Authorization: Bearer <ANON_KEY>"

ตารางภายในต้องได้ [] ถ้าได้ข้อมูลจริงกลับมา แปลว่ายังเปิดอยู่

ใน dashboard ของ Supabase มีหน้าแจ้งเตือนความปลอดภัย (security advisor) ที่บอกว่าตารางไหนยังไม่เปิด RLS เปิดดูหลังสร้างตารางใหม่ทุกครั้ง

เช็คลิสต์

  • เปิด RLS ทุกตารางใน schema public ตั้งแต่สร้าง
  • ตารางที่มีแค่เซิร์ฟเวอร์ใช้: ไม่ต้องมี policy ให้ anon
  • ตารางที่หน้าเว็บอ่าน: policy เฉพาะ select และกรองเฉพาะแถวที่ควรเห็น
  • service_role อยู่บนเซิร์ฟเวอร์และตัวแปรสภาพแวดล้อมเท่านั้น ไม่อยู่ในไฟล์ที่ commit
  • ข้อมูลอ่อนไหว (ข้อมูลสุขภาพ เบอร์โทรลูกค้า) อย่าให้หน้าเว็บอ่านตรง ให้ผ่าน API ของเซิร์ฟเวอร์ที่ตรวจสิทธิ์แล้ว

อ่านต่อ: ย้าย state ไปเก็บใน Supabase

เกี่ยวกับผู้เขียน — Jark นักพัฒนา LINE Bot และระบบ AI อัตโนมัติ อยู่เชียงใหม่ ทำระบบตอบแชทและจองคิวให้ร้านค้าจริง บทความในเว็บนี้มาจากงานที่ลงมือทำเอง อ่านเพิ่ม