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
Realtime Authorization is in Public Alpha. To use Authorization for your Realtime Channels, use supabase-js
version v2.44.0
or later.
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
optionprivate: true
Increased RLS complexity can impact database performance and connection time, leading to higher connection latency and decreased join rates.
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.
_10create policy "authenticated can read all messages on topic"_10on "realtime"."messages"_10for select_10to authenticated_10using (_10 (select realtime.topic()) = 'room-1'_10);
Examples
The following examples use this schema:
_23create table public.rooms (_23 id bigint generated by default as identity primary key,_23 topic text not null unique_23);_23_23alter table public.rooms enable row level security;_23_23create 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_23alter table public.profiles enable row level security;_23_23create 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_23alter 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 (select
s) for users who are linked to the requested topic within the relationship table public.room_users
:
_16create policy "authenticated can receive broadcast"_16on "realtime"."messages"_16for select_16to authenticated_16using (_16exists (_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
.
_19import { createClient } from 'npm:@supabase/supabase-js@2.38.5'_19const url = 'https://<project_ref>.supabase.com'_19const apikey = '<api_key>'_19_19const client = createClient(url, apikey)_19_19const channel = client.channel('room-1', {_19 config: { private: true },_19})_19_19channel_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
:
_16create policy "authenticated can send broadcast on topic"_16on "realtime"."messages"_16for insert_16to authenticated_16with 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
.
_16create policy "authenticated can listen to presence in topic"_16on "realtime"."messages"_16for select_16to authenticated_16using (_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
.
_16create policy "authenticated can track presence on topic"_16on "realtime"."messages"_16for insert_16to authenticated_16with 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.
_16create policy "authenticated can listen to broadcast and presence on topic"_16on "realtime"."messages"_16for select_16to authenticated_16using (_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.
_16create policy "authenticated can send broadcast and presence on topic"_16on "realtime"."messages"_16for insert_16to authenticated_16with 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.