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.
Additional context:
- I signed the JWT using the same secret set in
SUPABASE_JWT_SECRET
. - I confirmed that the client Supabase instance includes the custom JWT in the
Authorization: Bearer
header. - The payload includes:
{ "sub": "<user-uuid>", "aud": "authenticated", "role": "authenticated", "email": "<user-email>", "exp": <timestamp> }
- Realtime is enabled on the
messages
table. - My client uses Supabase’s JS SDK v2 with
.channel()
.
Question:
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).