Storage and Realtime

Authorize Realtime channels

Use private channels, topic design, RLS-backed authorization, cleanup, and reconnect behavior without leaking events across tenants.

9 minute lesson

~~~

A channel topic is part of the authorization boundary. room:42 is not a decorative label — it is the value your policies inspect to decide who may join. Use stable tenant or room identifiers, never guessable convenience names built from emails or usernames.

By default, channels are open to any client that knows the topic. Mark them private so Realtime enforces authorization at join time:

const channel = supabase.channel('room:42', {
  config: { private: true },
})

Private channels are authorized with RLS policies on the realtime.messages table. A select policy controls who may join and receive; an insert policy controls who may send. Verify membership against your own data:

create policy "members receive room events"
on realtime.messages for select
to authenticated
using (
  exists (
    select 1 from room_members
    where room_members.room_id =
      split_part(realtime.topic(), ':', 2)::bigint
      and room_members.user_id = auth.uid()
  )
);

realtime.topic() returns the topic the client asked to join. The policy checks it against the room_members table, so membership — durable, queryable, revocable — is the source of truth for who hears what.

Test it with two private rooms and two users. Ada is a member of room 42, Grace is not. Ada’s subscribe() callback reports SUBSCRIBED. Grace’s reports an error status instead of silently joining. If Grace gets in anyway, check that the client really passed private: true — forgetting that flag is the common hole, and nothing looks wrong because members still connect fine.

Clean up and expect messiness

Remove subscriptions when a client leaves:

await supabase.removeChannel(channel)

A leaked subscription keeps receiving events and keeps consuming connection quota, and in UI components it double-handles every message after a remount.

Then design for the transport’s honesty: expect reconnects, duplicates, gaps, and out-of-order application effects where the transport does not promise otherwise. A client that was offline for ten seconds missed whatever was broadcast during them — Realtime does not replay history. Keep durable truth in the right database table and treat channel events as hints: on reconnect, refetch state from the table, then resume listening.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →