Plan: Free
SDK: @supabase/supabase-js@2.74.0 (loaded as ESM from esm.sh)
API key type: new publishable key (sb_publishable_...)
JWT system: legacy (no auth.jwt_keys table; not using asymmetric signing keys)
Uploading a file to a Storage bucket (clinic-photos) from the browser
fails with HTTP 403 {"statusCode":"403","error":"Unauthorized","message":"new row violates row-level security policy"}, even though:
role: authenticated,
a ~936-char JWT present in localStorage and sent in the Authorization
header).fetch, explicitly
setting Authorization: Bearer <session_jwt> and apikey: <publishable_key>.
Still 403..from('clinics').select()) with the same session works fine.I simulated the exact policy logic in the SQL editor, impersonating the real
authenticated user (a valid admin UID) via SET LOCAL ROLE authenticated and
SET LOCAL request.jwt.claims. The three conditions of the Storage INSERT
policy were checked:
auth.uid() -> returns the admin's UID (OK)is_admin() -> returns TRUE (OK)EXISTS (SELECT 1 FROM clinics WHERE id::text = <folder> AND user_id = auth.uid()::text) -> returns TRUE (OK)All three return TRUE. So the database side is correct: if Storage passed the user's JWT to Postgres, the INSERT would be allowed.
-- on storage.objects, FOR INSERT
bucket_id = 'clinic-photos'
AND is_admin()
AND EXISTS (
SELECT 1 FROM clinics
WHERE id::text = split_part(name, '/', 1)
AND user_id = auth.uid()::text
)
is_admin() is SECURITY DEFINER, owner postgres, SET search_path = public, pg_temp, GRANT EXECUTE ... TO authenticated, anon. The admins
table has RLS enabled with one SELECT policy user_id = auth.uid().
clinics RLS policies: INSERT/UPDATE/DELETE require is_admin() AND user_id = auth.uid().is_admin(): exists, SECURITY DEFINER, owner postgres, returns TRUE in the
correct auth context.admins; the target clinic's user_id matches the
admin UID.authenticated.fetch to POST /storage/v1/object/clinic-photos/<path> with
explicit Authorization: Bearer <jwt> + apikey header -> still 403.Why does Storage reject the upload with row-level security policy when the
same JWT/UID makes every policy condition evaluate TRUE in Postgres? It looks
like Storage is not authenticating the request as the user (treating it as
anon) even though a valid session JWT is sent in Authorization. Is this
related to using the new sb_publishable_ key with Storage on a legacy-JWT
project? What configuration should I check?
signInWithPassword (valid admin).fetch POST to
https://<proj>.supabase.co/storage/v1/object/clinic-photos/<folder>/<file>
with headers Authorization: Bearer <session jwt>,
apikey: <publishable key>, Content-Type: <file type>, body = file.new row violates row-level security policy.The user encounters a 403 error when uploading a file to a Supabase Storage bucket, despite having a valid authenticated session and correct RLS policies. The issue persists even when bypassing the SDK and using a manual fetch request. The user suspects the problem might be related to the use of a new publishable key with a legacy JWT system.
Are you doing upsert option with the insert?
If so you have to meet select and update policies also.