I'm building a website using Next.js, NextAuth, and Supabase. I'm not using Supabase's built-in auth — instead, I manage a custom users
table and generate custom JWTs using NextAuth
during sign-in.
In the JWT, I set the sub
claim to the user's UUID from my own users
table, and use this to authenticate against Supabase's RLS. Here's a simplified version of the policy I'm using:
CREATE POLICY "Allow users to select their own messages"
ON messages FOR SELECT
USING (auth.uid()::text = user_id::text);
Direct select
queries using the authenticated Supabase client work as expected and return only the current user's messages.
Realtime subscriptions (.channel().on('postgres_changes')
) connect successfully but do not receive any data after new messages are inserted.
SUPABASE_JWT_SECRET
.Authorization: Bearer
header.{
"sub": "<user-uuid>",
"aud": "authenticated",
"role": "authenticated",
"email": "<user-email>",
"exp": <timestamp>
}
messages
table..channel()
.Why is Supabase Realtime not enforcing RLS correctly with a custom JWT, even though direct queries work?
How can I make Realtime subscriptions respect RLS policies using auth.uid()
when using a custom users
table and manually generated JWT?
Or is it other problem causing the issue?
To test whether this is an RLS issue, I temporarily added a policy that allows all users to select any rows:
CREATE POLICY "temp_test_realtime"
ON messages FOR SELECT
USING (true);
After enabling this, Realtime subscriptions started receiving data correctly for new inserts.
So it seems the issue is related to how Supabase Realtime interacts with RLS when using a custom JWT (not Supabase Auth).
In order to use custom JWT validation in RLS with realtime, you need to manually set the refreshed JWT token in Supabase realtime client too :
client.realtime.setAuth({your refreshed token})