How to use Pusher with private encrypted channels

Important when dealing with sensitive data

Joel Clermont
Joel Clermont
2024-03-26

When you're dealing with a third-party websockets provider, like Pusher, there are two things to consider regarding message security.

First, you want to make sure that only the correct users can see a message. Laravel provides a nice feature called private channels which adds a layer of authorization on top of the websocket channel. Now, a user cannot connect to a channel unless the Laravel app authorizes them.

Second, does it matter to you if Pusher can see the contents your messages as they pass through its servers. This depends on your application. For a hobby project, it might not matter, but if you're in an industry that is subject to strict privacy regulations, it's not enough for you to trust Pusher. You need to go a step further and encrypt the messages.

I recently needed to do this, and I couldn't find anything in the Laravel docs about it. But I knew Pusher supported it, so I dug into the Laravel Echo source code, and I was pleasantly surprised to see that encrypted channels were supported.

There are a few changes needed on both the server and the client to convert a private channel into a private encrypted channel:

// Step 1 - generate a key with `openssl rand -base64 24` and add it to .env
PUSHER_APP_ENCRYPTION_MASTER_KEY_BASE64=your-base64-encoded-key

// Step 2 - add one line in config/broadcasting.php
'pusher' => [
    // ... edited for clarity
    'options' => [
        // ... edited for clarity
        'encryption_master_key_base64' => env('PUSHER_APP_ENCRYPTION_MASTER_KEY_BASE64'),
    ],
],

// Step 3 - use the EncryptedPrivateChannel in your Event class
public function broadcastOn()
{
    return new EncryptedPrivateChannel('your-channel-name');
}

// Step 4 - use the pusher-js library that supports encryption in app.js
window.Pusher = require('pusher-js/with-encryption');

// Step 5 - use the encrypted channel in your frontend code
window.Echo.encryptedPrivate('your-channel-name').listen('your-event', /* your callback */);

With this configuration in place, Laravel and Echo do all the hard work of encrypting and decrypting the messages for you.

Depending on your needs, it might also be worth considering using a self-hosted option like Laravel Reverb to skip the need for message encryption altogether.

Hope this helps,

Joel

P.S. I'll be a good open source citizen, and go submit a PR to the Laravel docs now too.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼

Level up your Laravel skills!

Each 2-minute email has real-world advice you can use.