Supabase Realtime not working with RLS
Bug report
- [x] I confirm this is a bug with Supabase, not with my own application.
- [x] I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
Supabase Realtime doesn't get emit events when provided with table name, but works without table name in localhost
To Reproduce
Steps to reproduce the behavior, please provide code snippets or a repository:
- Went to
/database/replicationand turned onsupabase_realtime
- Turned on table for which I want realtime events
In Javascript, If I do something like this, this won't work
supabase
.client
.channel('user_list_changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'users_list',
},
(payload: any) => console.log('payload', payload),
)
.subscribe();
but this would
supabase
.client
.channel('user_list_changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
},
(payload: any) => console.log('payload', payload),
)
.subscribe();
Expected behavior
I was expecting to receive events when provided with table_name
Screenshots
If applicable, add screenshots to help explain your problem.
System information
- OS: Mac
- Browser: Brave
- Version of supabase-js:
"@supabase/supabase-js@^2.10.0":
"@supabase/functions-js" "^2.1.0"
"@supabase/gotrue-js" "^2.46.1"
"@supabase/postgrest-js" "^1.8.0"
"@supabase/realtime-js" "^2.7.4"
"@supabase/storage-js" "^2.5.1"
```
- Version of Node.js: 18.3.0
I cannot reproduce this issue. Are you able to share a concrete repro-case and possibly console logs?
supabase.channel('filtered-channel')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'users_list'
},
(payload) => console.log('Filtered Channel Payload:', payload),
)
.subscribe();
supabase.channel('global-channel')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
},
(payload) => console.log('Global Channel Payload:', payload),
)
.subscribe();
console.log('Listening for events');
@kamilogorek
Both of these don't log on update when I have enabled RLS policy on table
React.useEffect(() => {
console.log('here');
serviceSubscription.current = supabase
.getClient()
.channel(`service-data-${serviceName}`)
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'services_connected',
},
(payload) => console.log('Change', payload),
)
.subscribe();
supabase
.getClient()
.channel(`service-data`)
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
},
(payload) => console.log('Global', payload),
)
.subscribe();
}, []);
Where
class Supabase {
client: SupabaseClient<Database>;
constructor() {
const token = localStorage.getItem(SUPABASE_LOCAL_STORAGE_TOKEN_NAME);
if (token) {
this.client = createClient<Database>(VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY, {
global: {
headers: {
Authorization: `Bearer ${token}`,
},
},
});
} else {
this.client = createClient<Database>(VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY);
}
}
createClient(authToken: string) {
this.client = createClient<Database>(VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY, {
global: {
headers: {
Authorization: `Bearer ${authToken}`,
},
},
});
}
getClient(): SupabaseClient<Database> {
return this.client;
}
}
const supabase = new Supabase();
Here is my RLS policy. (Normal Select queries work as expected).
@kamilogorek Any update on this?
@RentfireFounder it's working locally but not working when using Supabase infrastructure?
@filipecabaco no it's working when RLS is disabled but not working when RLS is enabled.
With RLS enabled, the select/update and normal queries work but realtime doesn't
if this is the problem with my RLS rules, why is normal queries working and not realtime?
Also, more on this comment: https://github.com/supabase/supabase-js/issues/1733
@RentfireFounder just to confirm you're spinning up the Supabase stack locally correct? This is a locally running version of Realtime that RLS is not working for you. Is that right?
@w3b6x9 Yap!
@w3b6x9 @filipecabaco @kamilogorek were you able to replicate it? any updatea?
@RentfireFounder sorry for the radio silence, we had a couple of outstanding issues...
One of them could be related with this: what is the URL you are using to connect?
Hello, i got the same error... my RLS are well executed (log in a postgres function) but the realtime does not return the change @filipecabaco
Exact same thing is happening with me, (working with only prod) with RLS turned on i only get DELETE events. No matter what RLS policy I add I dont see any events coming in. Like the OP said on turning RLS off again postgres changes start flowing again.
UPDATE: I added a SELECT policy with anon role set as TRUE (not recommended but it worked for now and can carry on with the dev)
@filipecabaco @w3b6x9 i am using localhost
hey everyone, sorry we were working on a lot of changes and were not able to dedicate enough time to close this issue.
is this issue persisted? were you able to tackle it?
@filipecabaco nope, Also, just in case, did you see this comment? could it be because I am creating my own jwt and using that>
We do have an issue currently with custom jwts where the errors are not properly shown to the user 😞 I wonder if this is related 🤔
Exact same thing is happening with me, (working with only prod) with RLS turned on i only get DELETE events. No matter what RLS policy I add I dont see any events coming in. Like the OP said on turning RLS off again postgres changes start flowing again.
UPDATE: I added a SELECT policy with anon role set as TRUE (not recommended but it worked for now and can carry on with the dev)
I'm having the same issue. Any updates here?
Having the same issue. Has anyone been able to solve this?
Same for me, anyone had any luck?
I'm also experiencing this issue. I'm using an RLS policy that checks a custom JWT created using the supabase jwt secret (with the user matching a third party auth token). My RLS policy is working fine with regular requests but not realtime requests.
When I turn off the RLS (or just set it to USING (true)) I get all the events. When I have it on I only get DELETE events.
I was able to get it working. See this article: https://liviogamassia.medium.com/using-supabase-rls-with-firebase-auth-custom-auth-provider-357eaad9c70f for a solution.
In my case I was already correctly setting role and aud = 'authenticated', but the thing I needed to do was also use realtime.setAuth(supabaseJwt):
const supabaseJwt = generateSupabaseJwt(
supabaseJwtInfo,
SUPABASE_JWT_SECRET
);
const supabaseClient = createClient<Database>(
SUPABASE_URL,
SUPABASE_ANON_KEY,
{
auth: {
persistSession: false,
autoRefreshToken: false,
detectSessionInUrl: false,
},
global: {
headers: {
Authorization: `Bearer ${supabaseJwt}`,
},
},
}
);
supabaseClient.realtime.setAuth(supabaseJwt);
Root Cause When using custom JWT tokens with Supabase: Regular API calls use the Authorization header in global.headers Realtime subscriptions need a separate authentication call via realtime.setAuth() Without realtime.setAuth(), realtime runs as anonymous and RLS blocks it
Key Points ✅ Both global.headers.Authorization AND realtime.setAuth() are required ✅ Call realtime.setAuth() immediately after client creation ✅ Call realtime.setAuth() whenever you update tokens ✅ This works with any custom JWT token (Firebase Auth, custom auth, etc.)
@filipecabaco I think this can get closed?
Potentially yes but we did release a new version of supabase-js that I hope will remove the need to have setAuth
https://github.com/supabase/supabase-js/pull/1551
the issue seemed to have been a mix of problems:
- not calling it for sign in changes ( 🤦 )
- using binded functions might have caused issues a timing issue when assigning the token so calling it directly will reduce the probability of that happening
Wondering if my issues are something to do with this. Since updating
"@supabase/supabase-js": "2.53.0" -> "@supabase/supabase-js": "2.57.4", which included a few real time changes from this package, my realtime subscriptions broke 9/10 times. Occasionally they would work surprisingly but most of the time they wouldn't.
Downgrading my package again fixed the issue.
I have realtime all throughout my app and have a base hook that higher-order hooks leverage:
const realtimeState = useRealtimeBase({
config: {
channelName: `item:${id}:items`,
table: 'my_table',
event: RealtimeEvent.All,
filter: `myId=eq.${myId}`,
enabled: enabled && !!myId,
debugLabel: '...',
},
onPayload: handlePayload,
dependencies: [threadId],
});
and in my realtime base I have something like
const channel = supabase
.channel(channelName)
.on(
'postgres_changes',
{
schema: 'public',
table,
event,
...(filter && { filter }),
},
(payload: RealtimePostgresChangesPayload<Record<string, object>>) => {
enableLogging && log('Received payload:', payload);
onPayloadRef.current?.(payload);
},
)
.subscribe(handleStatus);
channelRef.current = channel;
};
Hi @mandarini ,
Are there plans to fix this issue, this issue makes realtime impossible for multi tenant apps that use a x-org-slug header to determine what access to allow.
@IdrisCelik for multi tenant applications I would advise to use a custom JWT that includes the organisation so that way you can use those claims in the RLS policies instead of using headers.
Headers are very limitative when it comes to establishing websocket connections so we opted to go with this route instead
Check out the docs here:
- https://supabase.com/docs/guides/auth/auth-hooks/custom-access-token-hook
- https://supabase.com/docs/guides/auth/jwts?queryGroups=language&language=ts Specifically this: https://supabase.com/docs/guides/auth/jwts?queryGroups=language&language=ts#using-custom-or-third-party-jwts
@IdrisCelik for multi tenant applications I would advise to use a custom JWT that includes the organisation so that way you can use those claims in the RLS policies instead of using headers.
Headers are very limitative when it comes to establishing websocket connections so we opted to go with this route instead
Hi @filipecabaco ,
Thanks for your reply! Unfortunatly this doesn't cut it for my app requirements. Having a custom claim currentTenantId would definitly work. But it will break when the same auth user wants to use one tenant on one device, and another on another device. Since claims are set for the auth user and not a specific device for the auth user.
Hey, I was experiencing similar issues where RLS was setup correctly, and Realtime worked fine when RLS was disabled. However, when RLS was enabled, no changes were sent back to the client (even though standard SELECT queries worked fine).
The issue for me was that the socket was establishing the connection before the Auth Session was fully loaded. When the component mounts, there is a brief delay while Supabase retrieves the session token from storage. If you call .subscribe() immediately, the WebSocket connects using the anon role. Consequently, any RLS policy relying on auth.uid() fails silently, blocking the events.
I fixed it by wrapping the subscription in an async function and explicitly awaiting the session before subscribing.
Maybe this will be of help to someone :D
`useEffect(() => { const supabase = createClient(); let channel = null;
const setupSubscription = async () => { // 1. CRITICAL: Wait for the session to load. // Without this, the socket connects as 'anon' and RLS fails. const { data: { session } } = await supabase.auth.getSession();
if (!session) return;
// 2. Now subscribe (Supabase will use the authenticated token)
channel = supabase
.channel("my_channel")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "my_table" },
(payload) => {
console.log("Change received!", payload);
}
)
.subscribe();
};
setupSubscription();
return () => { if (channel) supabase.removeChannel(channel); }; }, []);`