Replies: 5 comments 1 reply
|
What role is the policy set for with a TO if any? Check the API Gateway log to see if you are sending in the Authenticated user ID or log the auth.jwt() value in your rpc. |
|
Solved — root cause was a self-referential SELECT policy, not auth/role/trigger issues. Thanks for the diagnostic pointers — to answer them directly, since they ruled out most of the usual suspects and pointed us at the right area: Role/TO on the policy: default (no TO clause → applies to PUBLIC, gated by WITH CHECK/USING as normal). Not the issue. This is why it "worked" everywhere except the real API call: A bare SQL-editor insert ... values (...); with no RETURNING never triggers this re-check at all. Fix: add a second, non-recursive SELECT policy that grants visibility with a plain column check instead of a subquery against the same table, e.g.: create policy weddings_owner_select on public.weddings Takeaway for anyone else hitting "works in SQL Editor, fails through the Data API" with RLS: if your SELECT policy queries the same table it's attached to (directly or through a helper function), suspect the RETURNING re-check first. Test by sending Prefer: return=minimal — if that makes the insert succeed, this is your bug. |
|
Classic Check: SELECT policyname, cmd, roles, qual, with_check
FROM pg_policies WHERE tablename IN ('table1', 'table2');
-- Look for: cmd = 'INSERT', qual = NULL, with_check = 'true'Fix (FOR ALL covers everything): DROP POLICY IF EXISTS "your_policy" ON table1;
CREATE POLICY "owner_all" ON table1
FOR ALL TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Repeat for table2Why SQL Editor passes: Free audit script: https://github.com/cekuu35/supabase-rls-leak-demo |
|
Glad you found it. One correction for anyone who lands here from the error message, because the last comment misdiagnoses it and the wrong fix is easy to cargo-cult.
The mechanism behind your actual root cause is worth stating explicitly, because it is the thing that produces the "works in the SQL editor, fails through the API" asymmetry that had you chasing JWT claims: Postgres applies The one-line test that separates the two cases, which is much faster than auditing policies: // no .select() → Prefer: return=minimal → no RETURNING → SELECT policy never runs
const { error } = await supabase.from('weddings').insert({ ... })If that succeeds and the Worth knowing because the debugging instinct on a |
|
Follow-up, because I went looking for the exact wording and found two things worth leaving here for whoever lands on this from the error message. The behaviour is documented, in The nastier part, if a Worth knowing because the standard advice is to mark policy helpers |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Every authenticated INSERT into public.weddings or public.vendors (a row the caller legitimately owns, owner_id = their own auth.uid()) is rejected via the Data API with:
42501 - new row violates row-level security policy for table "weddings" (or "vendors")
100% reproducible, via every path tested: our own SECURITY INVOKER RPC wrappers (create_wedding/create_vendor), a raw POST /rest/v1/weddings, and the real production app itself (browser signup via @supabase/supabase-js).
DEFINITIVE PROOF POSTGREST IS SETTING UP THE CORRECT AUTH CONTEXT:
We created this debug function and called it via the exact same client/token, in the exact same request pattern, immediately before the failing insert:
create function public.debug_request_jwt() returns jsonb language sql stable as $$
select jsonb_build_object('current_user', current_user, 'auth_uid', auth.uid(),
'claims', current_setting('request.jwt.claims', true));
$$;
Result: current_user = "authenticated", auth_uid = the test user's real id (byte-for-byte match), claims JSON complete and correct (role: authenticated, sub matching). The very next call, same token, same connection pattern - create_wedding (whose only relevant check is owner_id = auth.uid(), and which builds the row via values (auth.uid(), ...)) still fails with the same 42501.
ADDITIONAL RULED-OUT CAUSES:
pg_policies confirms no RESTRICTIVE policies exist; INSERT policies are exactly owner_id = auth.uid() (checked permissive/roles columns).
The identical insert in the SQL editor, simulating the exact real JWT claims for the same user, succeeds every time.
Full project restart made no difference.
Migrating from legacy anon/service_role keys to the new publishable/secret keys made no difference.
No RETURNING/SELECT-policy issue - confirmed directly that zero rows are created on the failed attempts (not just unreturned).
No type mismatch - owner_id and auth.uid() are both uuid.
Token signature is trusted - GET /auth/v1/user with the exact failing token returns 200 with full user data.
So: role switching, JWT claims, auth.uid() resolution, and token trust are all provably correct at the moment of the request. The failure appears to be inside RLS enforcement for these two tables specifically, at the Postgres/PostgREST layer, in a way we can't inspect further from the client side.
IMPACT: blocks every real couple/vendor signup on our live site right now.
Any currently facing the same issue or solved this? any help will be much appreciated.
All reactions