1

I am having trouble getting realtime data from my Supabase database when using Row level security (RLS) and a custom JWT.

When getting data from the database normally things work as expected, but when I watch for changes using the Realtime functionality no events fire.

Here is my code (I'm using Astro):

import { createClient } from "@supabase/supabase-js";


const supabase = await createClient(import.meta.env.PUBLIC_SUPABASE_URL, import.meta.env.PUBLIC_SUPABASE_KEY, {
        global: {
            headers: {
                Authorization: `Bearer ${jwt}`,
            },
        },
    });


supabase.from('notes').select().then(({data, error}) => {
    console.log(data, error) // This works, my notes data is in the console.
})

// I am unsure if these 2 lines are needed but they endless googling lead me to adding them.

supabase.realtime.setAuth(jwt)
supabase.realtime.accessToken = jwt

// HERE IS THE PROBLEM. When the notes table is updated, I don't see anything in the console.
    
supabase
    .channel('notes')
    .on('postgres_changes', { event: '*', schema: '*', table: 'notes' }, payload => {
        console.log("notes db change detected:", payload) // This log never appears
    })
    .subscribe()

My RLS on the notes table currently looks like this. Obviously I want to check a bit more than just if the JWT contains an email, but if I could get this to work it would be a good start.

  (((current_setting('request.jwt.claims'::text, true))::json #>> '{email}'::text[]) IS NOT NULL)

RLS on subabase dashboard

This failing suggests that the JWT doesn't contain an email, but then why does is work when fetching data normally (not watching for realtime changes)?

Here is my JWT setup (using Jose library).

const signingSecret = import.meta.env.SUPABASE_JWT_SECRET;
if (signingSecret) {
    session.supabaseAccessToken = await new jose.SignJWT({ email: user.email, role:     "authenticated" })
    .setProtectedHeader({ alg: 'HS256' })
    .setAudience("authenticated")
    .setExpirationTime(Math.floor(new Date(session.expires).getTime() / 1000))
    .setSubject(user.id)
    .sign(new TextEncoder().encode(signingSecret))
}

I have tried testing this in the Supabase inspector tab (joining the notes channel) and I get the following results:

  • When a service_role user, I can see the realtime updates.
  • When an anon_user, I cannot see the realtime updates.

I have also taken a look at my developer tools network tab and I can see that the websocket connection is working (I can see the heartbeat event and sync events), but no postgres events are being transmitted.

I am finding it very difficult to debug the RLS policy because I am not very familiar with databases and there doesn't appear to be any easy way to log what is going on when an RLS policy fails.

1 Answer 1

0

this worked for me using clerk to generate the token

supabase.realtime.setAuth(jwt)
import { useAuth } from "@clerk/nextjs";
const { getToken } = useAuth();

const token = await getToken({ template: "supabase" });
      if (!token) {
        console.warn(`No token found for ${tableName}`);
        return;
      }

      supabase = createClient(token);
      supabase.realtime.setAuth(token); // this is what enebled realtime to work for me.

I had a separate function for creating a client in Nextjs

export const createClient = (token: string) =>
  createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      global: {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      },
    }
  );

My tables were in the public schema so I didn't need to add any RLS policies to "realtime"."messages"
I just had a simple "Allow all for Authenticated Users" Policy on each table that I was subscribing to but I'm pretty sure this would work for just selecting.

alter policy "allow all for auth"
on "public"."Table"
to authenticated
using (
    true
);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.