Laravel Reverb: Private &amp; Presence Channel Auth at Scale | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Laravel Broadcasting with Reverb: Per-Channel Authorization and Presence Channels at Scale

 Laravel Broadcasting with Reverb: Per-Channel Authorization and Presence Channels at Scale
===========================================================================================

 Go beyond basic event broadcasting. Learn how to lock down private and presence channels with fine-grained authorization, manage large presence sets efficiently, and avoid the common pitfalls that surface in production Reverb deployments.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 16 Jun 2026 · Updated 16 Jun 2026 · 3 min read

ShareCopy linkCopied

 ![Laravel Broadcasting with Reverb: Per-Channel Authorization and Presence Channels at Scale](https://cdn.msaied.com/208/b654521431b6021da07c8209170ed9e9.png) 

  On this page +1. [Why Channel Authorization Deserves More Attention](#why-channel-authorization-deserves-more-attention)
2. [Structuring Channel Routes for Real Authorization](#structuring-channel-routes-for-real-authorization)
3. [Presence Channels: The join Return Value Matters](#presence-channels-the-codejoincode-return-value-matters)
4. [Scaling Presence Sets](#scaling-presence-sets)
5. [Broadcasting Events Only to Authorized Channels](#broadcasting-events-only-to-authorized-channels)
6. [Key Takeaways](#key-takeaways)

 Why Channel Authorization Deserves More Attention
-------------------------------------------------

Most tutorials stop at `Auth::check()` inside a channel route. In production, that is rarely enough. A multi-tenant SaaS, a collaborative document editor, or a live support dashboard each needs per-resource authorization that mirrors the same rules enforced in your HTTP layer — not a weaker copy of them.

Laravel Reverb is a first-party WebSocket server, but the authorization model is still driven by `routes/channels.php` and the `BroadcastServiceProvider`. Getting that layer right is the real work.

Structuring Channel Routes for Real Authorization
-------------------------------------------------

Avoid anonymous closures for anything non-trivial. Register a dedicated channel class instead:

```bash
php artisan make:channel OrderChannel

```

```php
// routes/channels.php
Broadcast::channel('orders.{orderId}', OrderChannel::class);

```

```php
// app/Broadcasting/OrderChannel.php
final class OrderChannel
{
    public function join(User $user, int $orderId): bool|array
    {
        $order = Order::findOrFail($orderId);

        // Reuse the same policy you use in controllers.
        return $user->can('view', $order);
    }
}

```

Using `$user->can()` delegates to your existing `OrderPolicy::view()` method. One rule, two enforcement points — no drift.

Presence Channels: The `join` Return Value Matters
--------------------------------------------------

For presence channels, returning `true` is not enough. You must return an array; that array becomes the member metadata broadcast to all subscribers.

```php
public function join(User $user, string $roomId): bool|array
{
    $room = ChatRoom::findOrFail($roomId);

    if (! $user->can('join', $room)) {
        return false;
    }

    return [
        'id'     => $user->id,
        'name'   => $user->display_name,
        'avatar' => $user->avatar_url,
    ];
}

```

Keep this payload small. Every subscriber receives it on `pusher:member_added`. Sending eager-loaded relationships here is a common mistake that bloats payloads and slows join acknowledgement.

Scaling Presence Sets
---------------------

Reverb stores presence membership in Redis by default when you configure the `reverb` driver with a Redis connection. The key concern at scale is **join/leave storms** — a deploy or network blip causes hundreds of clients to reconnect simultaneously.

Mitigate this with exponential back-off on the client side (Laravel Echo supports this via the `authEndpoint` retry config) and by keeping your auth endpoint fast:

```php
// config/broadcasting.php — tune the Reverb connection pool
'reverb' => [
    'driver' => 'reverb',
    'key'    => env('REVERB_APP_KEY'),
    'secret' => env('REVERB_APP_SECRET'),
    'app_id' => env('REVERB_APP_ID'),
    'options' => [
        'host'   => env('REVERB_HOST', '0.0.0.0'),
        'port'   => env('REVERB_PORT', 8080),
        'scheme' => env('REVERB_SCHEME', 'http'),
    ],
],

```

Cache the authorization result for short-lived presence joins (5–10 seconds is safe) using a tagged cache keyed on `user:{id}:channel:{name}`:

```php
public function join(User $user, string $roomId): bool|array
{
    return Cache::tags(['channel-auth'])
        ->remember("user:{$user->id}:room:{$roomId}", 8, function () use ($user, $roomId) {
            $room = ChatRoom::findOrFail($roomId);
            if (! $user->can('join', $room)) {
                return false;
            }
            return ['id' => $user->id, 'name' => $user->display_name];
        });
}

```

Invalidate the tag when room membership rules change (e.g., a user is removed from a room).

Broadcasting Events Only to Authorized Channels
-----------------------------------------------

Use `broadcastOn` to return a typed channel, not a raw string:

```php
final class OrderStatusUpdated implements ShouldBroadcast
{
    public function __construct(private readonly Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel("orders.{$this->order->id}")];
    }

    public function broadcastWith(): array
    {
        return ['status' => $this->order->status->value];
    }
}

```

Using `PrivateChannel` (or `PresenceChannel`) ensures Reverb enforces the auth handshake before delivering the event.

Key Takeaways
-------------

- **Delegate to policies**: reuse `Gate`/`Policy` inside channel classes — never duplicate authorization logic.
- **Return arrays from presence `join`**: returning `true` silently breaks presence membership metadata.
- **Keep join payloads minimal**: large arrays on `pusher:member_added` degrade performance for all subscribers.
- **Cache short-lived auth results**: reduces DB pressure during reconnect storms without meaningful security trade-offs.
- **Use typed channel classes**: `PrivateChannel` and `PresenceChannel` make intent explicit and prevent accidental public exposure.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [reverb](https://www.msaied.com/public/articles?search=reverb)
- [broadcasting](https://www.msaied.com/public/articles?search=broadcasting)
- [websockets](https://www.msaied.com/public/articles?search=websockets)
- [real-time](https://www.msaied.com/public/articles?search=real-time)

 Frequently asked questions 
---------------------------

  Can I reuse Laravel policies inside channel authorization classes?Yes. Call `$user-&gt;can('action', $model)` inside your channel's `join` method. It delegates to the same Gate and Policy you use in controllers, keeping authorization logic in one place.

   What happens if a presence channel's `join` method returns `true` instead of an array?Returning `true` grants access but sends no member metadata. Other subscribers won't receive meaningful data in `pusher:member\_added` events, breaking any UI that displays who is online.

   How do I handle reconnect storms in Reverb with many presence channel subscribers?Cache the authorization result for a few seconds per user/channel pair using a tagged cache. This reduces database load during mass reconnects without weakening security, since the window is too short to be exploitable in practice.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleLaravel API Rate-Limiting: Custom Limiters, Per-Route Strategies, and Header Contracts](https://www.msaied.com/public/articles/laravel-api-rate-limiting-custom-limiters-per-route-strategies-and-header-contracts) [Next articleNew in Laravel 12: Features, Helpers, and Upgrade Notes](https://www.msaied.com/public/articles/new-in-laravel-12-features-helpers-and-upgrade-notes)  

   On this page
-------------

1. [Why Channel Authorization Deserves More Attention](#why-channel-authorization-deserves-more-attention)
2. [Structuring Channel Routes for Real Authorization](#structuring-channel-routes-for-real-authorization)
3. [Presence Channels: The join Return Value Matters](#presence-channels-the-codejoincode-return-value-matters)
4. [Scaling Presence Sets](#scaling-presence-sets)
5. [Broadcasting Events Only to Authorized Channels](#broadcasting-events-only-to-authorized-channels)
6. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
