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 ทั้งหมด ทำได้ทุกอย่าง |
เปิด 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