Realtime

Realtime Authorization


You can control client access to Realtime Broadcast and Presence by adding Row Level Security policies to the realtime.messages table. Each RLS policy can map to a specific action a client can take:

  • Control which clients can broadcast to a Channel
  • Control which clients can receive broadcasts from a Channel
  • Control which clients can publish their presence to a Channel
  • Control which clients can receive messages about the presence of other clients

How it works

Realtime uses the messages table in your database's realtime schema to generate access policies for your clients when they connect to a Channel topic.

By creating RLS polices on the realtime.messages table you can control the access users have to a Channel topic, and features within a Channel topic.

The validation is done when the user connects. When their WebSocket connection is established and a Channel topic is joined, their permissions are calculated based on:

  • The RLS policies on the realtime.messages table
  • The user information sent as part of their Auth JWT
  • The request headers
  • The Channel topic the user is trying to connect to

When Realtime generates a policy for a client it performs a query on the realtime.messages table and then rolls it back. Realtime does not store any messages in your realtime.messages table.

Using Realtime Authorization involves two steps:

  • In your database, create RLS policies on the realtime.messages
  • In your client, instantiate the Realtime Channel with the config option private: true

Helper functions

You can use the following helper functions when writing RLS policies:

realtime.topic

Returns the Channel topic the user is attempting to connect to.


_10
create policy "authenticated can read all messages on topic"
_10
on "realtime"."messages"
_10
for select
_10
to authenticated
_10
using (
_10
(select realtime.topic()) = 'room-1'
_10
);

Examples

The following examples use this schema:


_23
create table public.rooms (
_23
id bigint generated by default as identity primary key,
_23
topic text not null unique
_23
);
_23
_23
alter table public.rooms enable row level security;
_23
_23
create table public.profiles (
_23
id uuid not null references auth.users on delete cascade,
_23
email text NOT NULL,
_23
_23
primary key (id)
_23
);
_23
_23
alter table public.profiles enable row level security;
_23
_23
create table public.rooms_users (
_23
user_id uuid references auth.users (id),
_23
room_topic text references public.rooms (topic),
_23
created_at timestamptz default current_timestamp
_23
);
_23
_23
alter table public.rooms_users enable row level security;

Broadcast

The extension field on the realtime.messages table records the message type. For Broadcast messages, the value of realtime.messages.extension is broadcast. You can check for this in your RLS policies.

Allow a user to join (and read) a Broadcast topic

To join a Broadcast Channel, a user must have at least one read or write permission on the Channel topic.

Here, we allow reads (selects) for users who are linked to the requested topic within the relationship table public.room_users:


_16
create policy "authenticated can receive broadcast"
_16
on "realtime"."messages"
_16
for select
_16
to authenticated
_16
using (
_16
exists (
_16
select
_16
ru.user_id
_16
from
_16
rooms_users ru
_16
where
_16
ru.user_id = (select auth.uid())
_16
and ru.topic = (select realtime.topic())
_16
and realtime.messages.extension in ('broadcast')
_16
)
_16
);

Then, to join a topic with RLS enabled, instantiate the Channel with the private option set to true.


_19
import { createClient } from 'npm:@supabase/supabase-js@2.38.5'
_19
const url = 'https://<project_ref>.supabase.com'
_19
const apikey = '<api_key>'
_19
_19
const client = createClient(url, apikey)
_19
_19
const channel = client.channel('room-1', {
_19
config: { private: true },
_19
})
_19
_19
channel
_19
.on('broadcast', { event: 'test' }, (payload) => console.log(payload))
_19
.subscribe((status: string, err: any) => {
_19
if (status === 'SUBSCRIBED') {
_19
console.log('Connected!')
_19
} else {
_19
console.error(err)
_19
}
_19
})

Allow a user to send a Broadcast message

To authorize sending Broadcast messages, create a policy for insert where the value of realtime.messages.extension is broadcast.

Here, we allow writes (sends) for users who are linked to the requested topic within the relationship table public.room_users:


_16
create policy "authenticated can send broadcast on topic"
_16
on "realtime"."messages"
_16
for insert
_16
to authenticated
_16
with check (
_16
exists (
_16
select
_16
ru.user_id
_16
from
_16
rooms_users ru
_16
where
_16
ru.user_id = (select auth.uid())
_16
and ru.topic = (select realtime.topic())
_16
and realtime.messages.extension in ('broadcast')
_16
)
_16
);

Presence

The extension field on the realtime.messages table records the message type. For Presence messages, the value of realtime.messages.extension is presence. You can check for this in your RLS policies.

Allow users to listen to Presence messages on a Channel

Create a policy for select on realtime.messages where realtime.messages.extension is presence.


_16
create policy "authenticated can listen to presence in topic"
_16
on "realtime"."messages"
_16
for select
_16
to authenticated
_16
using (
_16
exists (
_16
select
_16
ru.user_id
_16
from
_16
rooms_users ru
_16
where
_16
ru.user_id = (select auth.uid())
_16
and ru.topic = (select realtime.topic())
_16
and realtime.messages.extension in ('presence')
_16
)
_16
);

Allow users to send Presence messages on a channel

To update the Presence status for a user create a policy for insert on realtime.messages where the value of realtime.messages.extension is presence.


_16
create policy "authenticated can track presence on topic"
_16
on "realtime"."messages"
_16
for insert
_16
to authenticated
_16
with check (
_16
exists (
_16
select
_16
ru.user_id
_16
from
_16
rooms_users ru
_16
where
_16
ru.user_id = (select auth.uid())
_16
and ru.name = (select realtime.topic())
_16
and realtime.messages.extension in ('presence')
_16
)
_16
);

Presence and Broadcast

Authorize both Presence and Broadcast by including both extensions in the where filter.

Broadcast and Presence read

Authorize Presence and Broadcast read in one RLS policy.


_16
create policy "authenticated can listen to broadcast and presence on topic"
_16
on "realtime"."messages"
_16
for select
_16
to authenticated
_16
using (
_16
exists (
_16
select
_16
ru.user_id
_16
from
_16
rooms_users ru
_16
where
_16
ru.user_id = (select auth.uid())
_16
and ru.topic = (select realtime.topic())
_16
and realtime.messages.extension in ('broadcast', 'presence')
_16
)
_16
);

Broadcast and Presence write

Authorize Presence and Broadcast write in one RLS policy.


_16
create policy "authenticated can send broadcast and presence on topic"
_16
on "realtime"."messages"
_16
for insert
_16
to authenticated
_16
with check (
_16
exists (
_16
select
_16
ru.user_id
_16
from
_16
rooms_users ru
_16
where
_16
ru.user_id = (select auth.uid())
_16
and ru.name = (select realtime.topic())
_16
and realtime.messages.extension in ('broadcast', 'presence')
_16
)
_16
);

Interaction with Postgres Changes

Realtime Postgres Changes are separate from Channel authorization. The private Channel option does not apply to Postgres Changes.

When using Postgres Changes with RLS, database records are sent only to clients who are allowed to read them based on your RLS policies.

Updating RLS policies

Client access polices are cached for the duration of the connection. Your database is not queried for every Channel message.

Realtime updates the access policy cache for a client based on your RLS polices when:

  • A client connects to Realtime and subscribes to a Channel
  • A new JWT is sent to Realtime from a client via the access_token message

If a new JWT is never received on the Channel, the client will be disconnected when the JWT expires.

Make sure to keep the JWT expiration window short.