SUPABASE CORE · 117
Row Level Security:把「誰能碰哪一列」放進 Database
前端 hidden button 不是 authorization;API filter 也可能漏。RLS 讓 PostgreSQL 在資料層依 authenticated context 對每筆 row 執行 policy。
Learning outcomes
- 能說明 RLS 與 frontend/application filter 差別。
- 能區分 USING 與 WITH CHECK 的核心概念。
- 能為四種 CRUD operation 思考 policy。
- 能設計多身分 policy integration tests。
1. Ownership model
courses
id
owner_id
title
score
Policy idea:
owner_id = auth.uid()2. SELECT:existing rows 可見性
USING (
owner_id = auth.uid()
)概念上,USING 限制 operation 能作用/看見哪些 existing rows。
3. INSERT / UPDATE new row
WITH CHECK (
owner_id = auth.uid()
)不能只限制 user A 看到自己資料,卻讓他 insert owner_id=user-B。
4. Operation-specific policy
| Operation | 問題 |
|---|---|
| SELECT | 哪些 rows 可見? |
| INSERT | 新 row 合法嗎? |
| UPDATE | 原 row 可改、改後仍合法嗎? |
| DELETE | 能刪哪一列? |
5. RLS ≠ Constraint
user A 可不可以碰 row X?
row X 本身是否合法?
Project checkpoint:Course Workspace v5
User A:
SELECT A rows PASS
SELECT B rows DENY
INSERT owner=A PASS
INSERT owner=B DENY
UPDATE A row PASS
DELETE B row DENY
Anon:
private rows DENY6. Policy 生效時,不一定會直接看到「Permission denied」
對某些 SELECT 情境,RLS 的效果可能表現成「查不到不允許看見的 rows」,而不是把每次查詢都轉成明顯 403。這代表 data=[] 本身不足以證明 table 真的沒有資料。
已知 database 有:
A row → owner_id = user-A
B row → owner_id = user-B
目前 session = user-A
SELECT courses
可能只得到:
[A row]
因此看到空結果時,要同時確認 authenticated identity、query filter、資料是否存在,以及 SELECT policy,而不是立即判定 Database 壞掉。
7. Debug evidence:用不同 identity 重播同一個 operation
Privileged context 可能 bypass 一般 policies,所以只用 admin 測試會掩蓋真實 user policy。最有價值的 evidence 是固定同一筆資料、同一個 query,只改 request 的 identity。
anon → expected DENY
user A → expected own rows
user B → expected B rows
admin → only where explicitly intended
如果 user A 能看到 B row,就往 policy expression / authenticated context 查;如果 admin 正常但 user A 全空,不代表 query syntax 一定錯,可能是 policy 根本沒允許一般 user。
8. Current RLS security traps
TO authenticated 不等於 ownership authorization
FOR SELECT
TO authenticated
USING (true)
這只代表「已登入角色可以進來」,沒有回答 user A 能不能讀 user B 的 row。Ownership model 仍要明確寫進 USING。
UPDATE 要同時想 existing row 與 new row
USING (
auth.uid() = user_id
)
WITH CHECK (
auth.uid() = user_id
)
USING 控制目前哪些 rows 可被 update;WITH CHECK 控制 update 後的新 row value 是否仍合法。缺少後者可能讓 owner 欄位被改成別人。
不要用 user-editable metadata 做 privileged authorization
user_metadata 可由 user 修改,因此不應拿來判斷 admin 等高權限角色。需要 JWT-based authorization metadata 時,應使用 server-controlled / app metadata 或其他可信來源。
Knowledge check
- Frontend owner filter 為何不能取代 RLS?
- USING / WITH CHECK 各關心什麼?
- 設計 A/B CRUD test matrix。
- score 0–100 應用 RLS 還是 CHECK?