To test Supabase Row Level Security (RLS), create two test users, add data as user A, then try to read and change it as user B and as a logged-out visitor. If B or a stranger gets A's rows, your policies are too loose. If A can't see their own rows, a policy is missing.
What you need
- Your own app, live or on staging with the same database rules.
- Two test accounts you create: A and B.
- Two browsers, or one normal and one private window.
Step 1: Create data as user A
Sign in as A and create one of everything your app stores: a project, a note, an upload. Open DevTools > Network, reload, and find the request to /rest/v1/.... Note the table and the row id.
Step 2: Try to read A's data as user B
- Change the ID in the URL. Open A's page address with A's id. B should get an empty page or an error.
-
Replay the request. Right-click a similar
/rest/v1/request > Copy as fetch, paste it in the Console and change the filter toid=eq.<A's id>. B should get[]. - Ask for everything. Replay without a user filter. B should only see B's rows.
Step 3: Try to change A's data as user B
Replay an update or insert with A's user_id in the body. It should fail or change 0 rows. If B can insert a row with A's user_id, the insert policy is missing with check (auth.uid() = user_id).
Step 4: Repeat logged out
Your public key is in the page, so a stranger can send the same request. Private tables should return [] or a permission error.
Step 5: The SQL editor way (optional)
The SQL editor normally bypasses RLS, so a plain select proves nothing. Impersonate a user and roll back:
begin;
set local role authenticated;
set local request.jwt.claims = '{"sub": "USER_B_UUID", "role": "authenticated"}';
select * from public.your_table where user_id = 'USER_A_UUID'; -- expect 0 rows
rollback;
Step 6: What RLS doesn't cover by itself
-
Views run as their owner unless created with
security_invoker = true. - security definer functions run with their owner's rights. Call them as B with A's id.
- Storage has its own policies. As B, open A's file URL. Private files should need a signed URL.
If a test fails
Paste this into your AI coder:
I tested my Supabase app with two users. User B could [read / change] user A's rows in [TABLE / VIEW / FUNCTION / BUCKET]. Please: 1) show me the current policies, 2) rewrite them so each user can only select, insert, update and delete rows where user_id = auth.uid(), with a matching WITH CHECK, 3) set views to security_invoker = true and check auth.uid() inside any security definer function, 4) give me the SQL and the two-account test to confirm the fix. Don't run anything until I say so.
Only test apps you own, with test accounts you created.
Free pre-launch checklist (14 checks to run before you share your link): https://milokits.com/checklist?ref=devto
We make Milo's Launch Check, a passive outside-in check for AI-built apps. It's launch week, so the Quick Check is $19 instead of $29 until 1 November (code QUICK19): https://milokits.com/?ref=devto#pricing
Top comments (0)