I'm building a template project for myself, and I've got Reverb and Echo working as intended with regular channels, but when using private channels, the /broadcasting/auth route returns a 401 Unauthorized with {"message":"Unauthenticated."} as its body.
I've been searching and debugging for a few hours so far, and none of the suggestions I found online completely fixed my issues.
I've set the api and auth:sanctum middleware on the ->withBroadcasting() in the bootstrap/app.php:
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
api: __DIR__ . '/../routes/api.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware
->web(append: [
HandleInertiaRequests::class,
AddLinkHeadersForPreloadedAssets::class,
])
->trustProxies('*')
->validateCsrfTokens();
})
->withBroadcasting('/../routes/channels.php', attributes: ['middleware' => ['api', 'auth:sanctum']])
->withExceptions()
->create();
My channels.php is empty, and the channel I'm trying to subscribe to is set using the BroadcastsEvents model trait on my User model:
class User extends Authenticatable
{
use HasApiTokens;
use HasFactory;
use HasProfilePhoto;
use Notifiable;
use TwoFactorAuthenticatable;
use BroadcastsEvents;
...
public function broadcastOn($event): PrivateChannel
{
return new PrivateChannel('users.' . $this->id);
}
}
My Echo is configured like so:
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
encrypted: true,
authorizer: (channel, options) => {
console.log('a');
return {
authorize: (socketId, callback) => {
console.log('b');
axios
.post(
'/broadcasting/auth',
{
socket_id: socketId,
channel_name: channel.name,
},
{
withCredentials: true,
headers: {
Accept: 'application/json',
},
}
)
.then(response => {
callback(false, response.data);
})
.catch(error => {
callback(true, error);
});
},
};
},
});
I'm subscribing to the event in an onMounted from my frontend:
onMounted(() => {
if (user.value) {
window.Echo.private(`users.${user.value.id}`).listen(
'.UserUpdated',
(event: { model: UserData }) => {
console.log('Received user update from reverb!', event);
}
);
}
});
Furthermore I'm using the Jetstream starter kit without any impactful changes to the default configs. The user making the request is authenticated to the regular frontend routes just fine.