ข้ามไปยังเนื้อหา

Securing the Data API

table หรือ function ต้องผ่าน สองด่านแยกกัน ก่อนที่ request จะทั้งปลอดภัยและสำเร็จ Data API exposure ตัดสินว่าเข้าถึงผ่าน API ได้เลยไหม ส่วน Row Level Security ตัดสินว่า request ที่เข้าถึงได้แล้วเห็นหรือแก้ row ไหนได้บ้าง

tutorial เก่า ๆ ของ Supabase หลายอันเล่าแค่เรื่อง RLS เหมือนว่า RLS คือทั้งหมด แต่ไม่ใช่ ก่อนที่ RLS จะถูกประเมินด้วยซ้ำ Postgres ต้อง grant สิทธิ์ให้ role ที่ request เข้ามา — anon, authenticated หรือ service_role — ให้แตะต้อง table หรือ function นั้นได้ก่อน ในโปรเจกต์ Supabase ปัจจุบัน ขั้นตอนนี้ไม่ได้เกิดขึ้นอัตโนมัติ คุณต้อง expose table และ function ที่ต้องการไว้ในหน้า Integrations → Data API ของ dashboard หรือเปิด Default privileges for new entities เพื่อให้ object ใหม่ที่สร้างใน schema public ได้สิทธิ์เข้าถึงไปเรื่อย ๆ

เบื้องหลังคือคำสั่ง grant ธรรมดาของ Postgres

-- Table grants: what each role is allowed to attempt
grant select on public.your_table to anon;
grant select, insert, update, delete on public.your_table to authenticated;
grant all on public.your_table to service_role;
-- Function grants: who may call this RPC endpoint
grant execute on function public.your_function to authenticated, service_role;

ถ้า role ไหนไม่มี grant บน table นั้นเลย table นั้นจะมองไม่เห็นสำหรับ role นั้นผ่าน Data API — request จะล้มเหลวก่อนที่ RLS จะถูกเช็คด้วยซ้ำ ไม่ว่า RLS policy ของคุณจะหลวมแค่ไหนก็ตาม นี่คือด่านระดับ table/function ที่ตอบคำถามว่า “role นี้แตะ object นี้ได้เลยไหม” ไม่ใช่ “เห็น row ไหนบ้าง”

เมื่อ table ผ่านด่าน exposure แล้ว Row Level Security (RLS) จะเข้ามาทำงานต่อและตอบคำถามที่แคบลง สำหรับ request ที่เข้ามาในนาม anon, authenticated หรือ user ที่ authenticated ตัวใดตัวหนึ่ง request นั้นเห็นหรือแก้ row ไหนได้บ้าง RLS policy เป็นหัวข้อของ module ถัดไปทั้งหมด ดังนั้นสิ่งที่ต้องจำจากบทนี้คือแค่เส้นแบ่งระหว่างสองชั้นนี้

  • Data API exposure — ระดับ table/function object นี้เข้าถึงผ่าน API ได้เลยไหม
  • Row Level Security — ระดับ row ในเมื่อเข้าถึงได้แล้ว request นี้เห็น row ไหนบ้าง

ทั้งสองต้อง config ให้ถูกทั้งคู่ มี exposure แต่ไม่มี RLS หมายความว่า table ที่เข้าถึงได้ทางเทคนิคจะคืนทุก row ให้ใครก็ตามที่ได้สิทธิ์เข้าถึง มี RLS แต่ไม่มี exposure หมายความว่า policy ที่ scope ไว้ถูกต้องแล้วจะไม่มี request ไหนไปถึงได้เลย เพราะ table ไม่เคยถูก expose ตั้งแต่แรก

-- Layer 2 lives on the table once it clears layer 1
alter table public.your_table enable row level security;
create policy "users read their own rows"
on public.your_table
for select
to authenticated
using (auth.uid() = user_id);

Postgres จะข้าม row level security ให้ role ใดก็ตามที่มี attribute BYPASSRLS และ service_role มี attribute นี้ request ที่ authenticate ด้วย service_role key ยังต้องผ่านด่าน 1 อยู่ (ต้องมี grant รองรับ แม้ปกติ service_role จะได้สิทธิ์กว้างอยู่แล้ว) แต่พอผ่านแล้ว RLS policy จะไม่ถูกใช้กับ service_role เลย role นี้เห็นและแก้ได้ทุก row นี่คือเหตุผลที่ service_role key ต้องไม่หลุดไปอยู่ใน browser หรือ mobile client เด็ดขาด สอดคล้องกับคำเตือนจาก module Foundations key นี้มีไว้สำหรับโค้ดฝั่ง server ที่เชื่อถือได้เท่านั้น ที่คุณต้อง enforce กฎการเข้าถึงเองแทนที่จะพึ่ง RLS

flowchart TB
  req["Incoming request as anon, authenticated, or service_role"] --> gate1{"Layer 1: Data API exposure — is the table/function reachable at all?"}
  gate1 -- "no grant" --> reject1["Rejected: not reachable"]
  gate1 -- "granted" --> role{"Role has BYPASSRLS?"}
  role -- "yes (service_role)" --> allrows["All rows visible, RLS skipped entirely"]
  role -- "no" --> gate2{"Layer 2: Row Level Security — which rows?"}
  gate2 -- "no matching policy" --> reject2["Rejected: no rows returned/modified"]
  gate2 -- "policy matches" --> scoped["Only matching rows visible/modifiable"]
Request ที่ผ่านด่าน exposure แล้วต่อด้วย RLS
ชั้น Data API exposure ควบคุมอะไรจริง ๆ
ถ้า table มี RLS policy ที่หลวมมาก แต่ไม่มี grant ให้ role authenticated เลย จะเกิดอะไรขึ้น
ทำไม request ที่ใช้ service_role key ถึงข้าม Row Level Security ไปทั้งหมด
ทำไม config แค่ Row Level Security อย่างเดียวถึงไม่พอ