Storage RLS rejects all uploads — auth.uid() NULL for ES256 JWTs (project rynswrhabeobtyjthpto) #48526
|
Support ticket already open: SU-435660. Posting here too since Free plan support has no guaranteed SLA and this blocks a real feature. Symptom: Every authenticated Storage upload fails with "new row violates row-level security policy" (403, AccessDenied) — on ALL buckets, reproduced identically on two separate buckets with standard "own folder" RLS policies. Already ruled out: RLS policy SQL is correct (verified live via supabase db query): bucket_id = 'X' AND auth.uid()::text = (storage.foldername(name))[1] Anyone else on ES256 signing keys seeing Storage RLS fail like this? Or know if Storage has a known gap with asymmetric JWTs? |
Replies: 2 comments 1 reply
|
Asymmetric keys work with storage. Are you getting storage errors or just RLS blocking? Did you check the storage log? How are you determining auth.uid() is null? Do you have the needed RLS policies for the type of upload you are doing? INSERT, SELECT, UPDATE? |
|
The error text is identical for two very different causes, and your own evidence points away from the one you're leaning toward. Worth separating them before assuming an ES256 gap. "new row violates row-level security policy" on a Storage upload is very often not the INSERT check failing — it's the SELECT after it. Storage runs Test it first, because it's decisive and takes a minute — add a SELECT policy mirroring your INSERT one: create policy "read own folder"
on storage.objects for select to authenticated
using ( bucket_id = 'X' and auth.uid()::text = (storage.foldername(name))[1] );If uploads start working, If it still fails, then measure alter policy "<your insert policy>" on storage.objects
with check ( (storage.foldername(name))[1] = coalesce(auth.uid()::text, 'NULL_UID') );Then upload one file into a folder literally named One mechanism that fits the "PostgREST fine, Storage not" split specifically: the two paths don't have to resolve the JWT the same way. |
Found it — and it was on our end, not a Supabase bug. Great question, that was exactly it.
We had NO SELECT policy at all on storage.objects for either bucket (only INSERT/UPDATE/DELETE). Our original migration reasoned "bucket is public, reads go through the public URL, no SELECT policy needed" — true for a browser hitting the public URL directly, but upload(..., { upsert: true }) needs to check whether an object at that path already exists first (an authenticated SELECT against storage.objects, subject to RLS) before deciding insert vs. update. With zero SELECT policy, that internal existence check always evaluated to "not visible", and the whole upsert failed closed with the RLS messag…