Realtime Broadcast and Presence Authorization

Apr 4, 2024

Update#

Discussion has been updated with solution chosen.

Realtime Authorization for Broadcast and Presence is now available in Public Beta.

See the official documentation.


Overview#

This post explains how authorization works for Realtime Broadcast and Realtime Presence.

This allows you (the developer) to control access to Realtime Channels. We use Postgres Row Level Security to manage access. Developers create Policies which allow or deny access for your users.

Usage#

Creating Realtime Policies#

Using Studio’s SQL editor you can set RLS rules against the table realtime.messages which will define the rules for your users.


_10
CREATE POLICY "presence sync and broadcast listen to authenticated users"
_10
ON realtime.messages FOR SELECT
_10
TO authenticated
_10
USING ( true );
_10
_10
CREATE POLICY "presence track and broadcast send to authenticated users"
_10
ON realtime.messages FOR INSERT
_10
TO authenticated
_10
WITH CHECK ( true );

Since you are using RLS policies you can do more complex examples.

In a scenario where you have a schema with a table for rooms and one that creates an association between rooms and users.

We'll use this example schema to be showcase RLS policies limiting Realtime functionality

We can build more complex RLS rules using this information:


_26
-- Set permission for authenticated users to only listen for Broadcast messages
_26
CREATE POLICY "authenticated can listen to broadcast only on their topics"
_26
ON realtime.messages FOR SELECT
_26
TO authenticated
_26
USING (
_26
exists(
_26
select 1
_26
from public.rooms r join public.rooms_users ru on r.id = ru.room_id
_26
where ru.user_id = auth.uid()
_26
and r.name = realtime.topic()
_26
and realtime.messages.extension = 'broadcast'
_26
)
_26
);
_26
-- Set permission for authenticated users to only write for Broadcast messages
_26
CREATE POLICY "authenticated can write to broadcast only on their topics"
_26
ON realtime.messages FOR INSERT
_26
TO authenticated
_26
WITH CHECK (
_26
exists(
_26
select 1
_26
from public.rooms r join public.rooms_users ru on r.id = ru.room_id
_26
where ru.user_id = auth.uid()
_26
and r.name = realtime.topic()
_26
and realtime.messages.extension = 'broadcast'
_26
)
_26
)

Testing Authorization#

Now to test it we can use a quick deno script by creating a index.ts


_21
// Run with deno run --allow-net --allow-env --allow-read --allow-ffi index.ts
_21
import { createClient } from "npm:@supabase/supabase-js@2.38.5";
_21
const url = "https://<project_ref>.supabase.com";
_21
const apikey = "<api_key>";
_21
_21
const client = createClient(url, apikey);
_21
_21
const channel = client.channel("channel_1", {
_21
config: { broadcast: { self: true }, private: true},
_21
});
_21
channel
_21
.on("broadcast", { event: "test" }, (payload) => console.log(payload))
_21
.on("presence", { event: "join" }, (payload) => console.log(payload))
_21
.on("presence", { event: "leave" }, (payload) => console.log(payload))
_21
.subscribe((status: string, err: any) => {
_21
if (status === "SUBSCRIBED") {
_21
console.log("Connected!");
_21
} else {
_21
console.error(err);
_21
}
_21
});

This will return an error with the message You do not have permissions to read from this Topic

But if we change our code to pass along an authenticated user, then we will be able to connect and receive / send messages.


_28
import { createClient } from "npm:@supabase/supabase-js@2.38.5";
_28
const url = "https://<project_ref>.supabase.co";
_28
const apikey = "<api_key>";
_28
_28
const client = createClient(url, apikey);
_28
_28
await client.auth.signInWithPassword({
_28
email: "<email>",
_28
password: "<password>",
_28
});
_28
_28
client.realtime.setAuth(
_28
(await client.auth.getSession()).data.session.access_token
_28
);
_28
const channel = client.channel("channel_1", {
_28
config: { broadcast: { self: true }, private: true },
_28
});
_28
channel
_28
.on("broadcast", { event: "test" }, (payload) => console.log(payload))
_28
.on("presence", { event: "join" }, (payload) => console.log(payload))
_28
.on("presence", { event: "leave" }, (payload) => console.log(payload))
_28
.subscribe((status: string, err: any) => {
_28
if (status === "SUBSCRIBED") {
_28
console.log("Connected!");
_28
} else {
_28
console.error(err);
_28
}
_28
});

Do not forget that RLS policies can use other tables in them so this will give you all the flexibility you need to better fit your use case but be aware of the performance impact of heavy RLS queries or non-indexed fields.

Migrating from Public Channels#

On connect, you need to send in the configuration that the channel will be private: true

Client library#

We’re working on the next version actively so we can provide a good developer experience.

Please check the latest next version at https://www.npmjs.com/package/@supabase/realtime-js?activeTab=versions

This library as changed the configuration settings to add private: true on channel connect to determine if the user will be connecting an RLS checked channel.

How it works#

Connection context#

When you connect with Realtime we set a connection configuration with your JWT, Topic and Headers using the following query:


_10
SELECT
_10
set_config('role', $1, true),
_10
set_config('realtime.topic', $2, true),
_10
set_config('request.jwt', $4, true),
_10
set_config('request.jwt.claims', $6, true),
_10
set_config('request.headers', $7, true)

This query is only run when you connect to a topic.

We’re also providing a new function to easily fetch the realtime.topic configuration with


_10
SELECT realtime.topic();
_10
_10
-- Usage example
_10
CREATE POLICY "authenticated users can only write to topic named foo"
_10
ON realtime.messages FOR INSERT
_10
TO authenticated
_10
WITH CHECK ( realtime.topic() = 'foo' );

Applying RLS Policies#

To achieve RLS checks on your Realtime connection we created a new table in the realtime schema to which you will be able to write RLS rules against it to control your topics extensions.

You won’t see any entries recorded in this table as we rollback the changes made to test out RLS policies to avoid creating clutter in your database.

Build in a weekend, scale to millions