RLS Check Failing on INSERT Despite 'WITH CHECK (true)' Policy (Works when RLS Disabled) #36619
Replies: 4 comments
|
You may also need to meet select policy if your REST call is asking to return data. If JS then you would have .select() or .single() in the insert call. For other clients the method varies and some it is the default to return data. |
|
Hi everyone, due to inactivity on this issue I've moved the issue over to discussions/enhancements. |
|
Hello! |
|
I ran into this exact same issue and eventually tracked down the root cause — sharing in case it saves someone else the same debugging spiral. It's not actually an RLS condition problem. When you chain .insert(...).select() (or .single()), PostgREST appends a RETURNING clause to the INSERT — and that RETURNING output is filtered by your table's SELECT policy, not just the INSERT policy's WITH CHECK. If there's no SELECT policy yet that makes the newly-inserted row visible (e.g. because visibility depends on a related row created by an AFTER INSERT trigger, or because there's no SELECT policy at all), the request fails with the RLS error — even if the INSERT's own WITH CHECK is literally true. I confirmed this by reproducing it on a brand-new, completely unrelated table with with check (true) and no SELECT policy: every plain .insert().select() failed with "violates row-level security policy", while a bare .insert() with no .select() chained after it succeeded immediately. Two ways to fix it: Add a permissive SELECT policy that covers the new row at insert time (tricky if visibility depends on something an AFTER trigger creates). Hope this saves someone the hours it cost me. 🙂 |
Uh oh!
There was an error while loading. Please reload this page.
I am experiencing a persistent Row Level Security (RLS) error (403 Forbidden / Postgres code 42501) when trying to INSERT a row into a specific table (
processo_assembleia) using the Supabase JavaScript client library. This happens even though the only applicable RLS policy for INSERT on this table for theauthenticatedrole isWITH CHECK (true).Crucially:
SET ROLE authenticatedandSET request.jwt.claims) succeeds.processo_assembleiatable allows the INSERT operation from the frontend client library to succeed.apikeyandAuthorization: Bearer <JWT>headers.sub(user ID),role('authenticated'), and is not expired.This suggests an issue with how the RLS check is evaluated for API requests when RLS is enabled, despite the policy logic being simply
true.Steps to Reproduce
processo_assembleia).authenticatedrole with the conditionWITH CHECK (true)and ensure no other INSERT policies exist for this table.@supabase/supabase-js).supabase.from('processo_assembleia').insert(...)). Ensure the payload includes a column referencing the user ID (e.g.,criado_por_id: user.id).SET ROLE authenticated; SET request.jwt.claims = '...'; INSERT INTO ...; RESET ROLE;- the insert also succeeds.Expected behavior
The
INSERToperation via the client library should succeed when RLS is enabled because theWITH CHECK (true)policy should always evaluate to true for an authenticated user.Actual behavior
The
INSERToperation fails with a 403 Forbidden error and a PostgreSQL errorcode: '42501', message: 'new row violates row-level security policy for table "processo_assembleia"'.Screenshots and Logs
WITH CHECK (true)):WITH CHECK (true)and emptyUSING expression."Successful SQL Editor Test:
SET ROLE authenticated;
SET request.jwt.claims = '{"sub":"791ea193*************", "role":"authenticated"}';
INSERT INTO public.processo_assembleia (
condo_id,
criado_por_id,
data_assembleia_prevista,
data_assembleia_primeira_chamada,
data_assembleia_segunda_chamada,
formato,
pauta,
status,
tipo_assembleia,
titulo
) VALUES (
10463,
'791ea193-****************',
'2025-05-03',
'2025-05-03T19:30:00',
'2025-05-03T19:50:00',
'Presencial',
'["Aprovação da última ata", "Aprovação da última ataasdasd", "sadasd"]',
'Reservada',
'AGO',
'Nova Assembleia'
);
RESET ROLE;
Failing Frontend Network Request:
*
*"Network tab showing the failing POST request with 403 status and presence of
Authorization+apikeyheaders."Console Error Message:
{ "code": "42501", "details": null, "hint": null, "message": "new row violates row-level security policy for table \"processo_assembleia\"" }Decoded JWT Payload:
{"iss": "https://[PROJECT_REF].supabase.co/auth/v1",
"sub": "[USER_ID]",
"aud": "authenticated",
"exp": 1745966186,
"iat": 1745962586,
"email": "[USER_EMAIL]",
"phone": "",
"app_metadata": {
"provider": "google",
"providers": [
"google"
]
},
"user_metadata": {
"avatar_url": "[AVATAR_URL]",
"custom_claims": {
"hd": "[USER DOMAIN]
},
"email": "[USER_EMAIL]",
"email_verified": true,
"full_name": "[USER_NAME]",
"iss": "https://accounts.google.com",
"name": "[USER_NAME]",
"phone_verified": false,
"picture": "[AVATAR_URL]",
"provider_id": "100159466417968246758",
"sub": "100159466417968246758"
},
"role": "authenticated",
"aal": "aal1",
"amr": [
{
"method": "oauth",
"timestamp": 1745962586
}
],
"session_id": "[SESSION_ID]",
"is_anonymous": false
}
```
* Texto: "Note: The
exptimestamp (1745966186) was verified to be valid (not expired) at the time the request failed."System information
Additional context
handle_updated_attrigger/moddatetimefunction)."All reactions