Step 8
Step 8 — Close database bypass paths with row-level security
0 views
Table of contents
An API may consistently add WHERE user_id = ?, but another SQL path or SDK can omit that check. Row-level security (RLS) makes the database compare the current principal with each row as the final access boundary.
Separate roles and policies
Keep anonymous and authenticated browser roles separate from server-only roles. Compare the JWT subject with owner_id for user-owned rows, and use separate policies for public reads and administrative writes. USING decides whether an existing row is visible; WITH CHECK decides whether the resulting new row is allowed.
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY documents_owner_read ON documents
FOR SELECT TO authenticated
USING (owner_id = auth.uid());
CREATE POLICY documents_owner_write ON documents
FOR INSERT TO authenticated
WITH CHECK (owner_id = auth.uid());
Do not use the service role as a universal key
Never send an RLS-bypassing server key to a browser; keep it inside the smallest server boundary. An admin UI check does not justify a database policy with an unconditional true. Record policy name, role, command, and condition in migrations, then compare the deployed database for drift.
Completion evidence
- Owner A cannot read or modify Owner B's rows.
- Anonymous, authenticated, administrator, and server roles have success and denial tests.
- Direct SQL and SDK paths enforce the same policy.
- Policy changes are tracked through migrations and production drift checks.
Related term: Row-Level Security
Terms in this content
🎉 You finished Web security foundations — JWT · OAuth · OWASP
What's next? Pick another course below.