# Realtime Authorization

Authorization for Supabase Realtime

You can control client access to Realtime [Broadcast](/docs/guides/realtime/broadcast) and [Presence](/docs/guides/realtime/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

To enforce private channels you need to disable the 'Allow public access' setting in [Realtime Settings](/dashboard/project/_/realtime/settings)

## 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 policies 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](/docs/guides/auth/jwts)
- 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`

Increased RLS complexity can impact database performance and connection time, leading to higher connection latency and decreased join rates.

## Accessing request information

### `realtime.topic`

You can use the `realtime.topic` helper function when writing RLS policies. It returns the Channel topic the user is attempting to connect to.

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

### JWT claims

The user claims can be accessed using the `current_setting` function. The claims are available as a JSON object in the `request.jwt.claims` setting.

```sql
create policy "authenticated with supabase.io email can read all"
on "realtime"."messages"
for select
to authenticated
using (
  -- Only users with the email claim ending with @supabase.io
  (((current_setting('request.jwt.claims'))::json ->> 'email') ~~ '%@supabase.io')
);
```

## Examples

The following examples use this schema:

```sql
create table public.rooms (
    id bigint generated by default as identity primary key,
    topic text not null unique
);

GRANT SELECT ON public.rooms TO anon;

alter table public.rooms enable row level security;

create table public.profiles (
  id uuid not null references auth.users on delete cascade,
  email text NOT NULL,

  primary key (id)
);

GRANT SELECT ON public.profiles TO anon;
GRANT SELECT, INSERT, UPDATE, DELETE ON public.profiles TO authenticated;

alter table public.profiles enable row level security;

create table public.rooms_users (
  user_id uuid references auth.users (id),
  room_topic text references public.rooms (topic),
  created_at timestamptz default current_timestamp
);

GRANT SELECT ON public.rooms_users TO authenticated;

alter table public.rooms_users enable row level security;

create policy "authenticated can read own room memberships"
on public.rooms_users
for select
to authenticated
using ((select auth.uid()) = user_id);
```

### 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`:

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

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

```javascript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('your_project_url', 'your_supabase_api_key')

// ---cut---
const channel = supabase.channel('room-1', {
  config: { private: true },
})

channel
  .on('broadcast', { event: 'test' }, (payload) => console.log(payload))
  .subscribe((status, err) => {
    if (status === 'SUBSCRIBED') {
      console.log('Connected!')
    } else {
      console.error(err)
    }
  })
```

```dart
final channel = supabase.channel(
  'room-1',
  opts: const RealtimeChannelConfig(private: true),
);

channel
    .onBroadcast(event: 'test', callback: (payload) => print(payload))
    .subscribe((status, err) {
  if (status == RealtimeSubscribeStatus.subscribed) {
    print('Connected!');
  } else {
    print(err);
  }
});
```

```swift
let channel = supabase.channel("room-1") {
  $0.isPrivate = true
}

Task {
  for await payload in channel.broadcastStream(event: "test") {
    print(payload)
  }
}

await channel.subscribe()
print("Connected!")
```

```kotlin
val channel = supabase.channel("room-1") {
    isPrivate = true
}
channel.broadcastFlow(event = "test").onEach {
    println(it)
}.launchIn(scope) // launch in your coroutine scope
channel.subscribe(blockUntilSubscribed = true)
println("Connected!")
```

```py
channel = realtime.channel(
  "room-1", {"config": {"private": True}}
)

await channel.on_broadcast(
  "test", callback=lambda payload: print(payload)
).subscribe(
  lambda state, err: (
    print("Connected")
    if state == RealtimeSubscribeStates.SUBSCRIBED
    else print(err)
  )
)
```

#### 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`:

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

### 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`.

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

#### 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`.

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

### 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.

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

#### Broadcast and Presence write

Authorize Presence and Broadcast write in one RLS policy.

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

## Interaction with Postgres Changes

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

Private and public channels can subscribe to Postgres Changes.

## Updating RLS policies

Client access policies 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 policies 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](/docs/guides/realtime/protocol#access-token)

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.