I don’t know how I missed this…
https://laravel.com/docs/9.x/sanctum#authorizing-private-broadcast-channels
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
I have installed laravel breeze --api, setup user auth working for seperate vue 3 api. I installed echo etc, and got public websocket broadcasting working. when I switch it to a private channel. It returns 401 unauthorized.
window.Echo = new Echo({
broadcaster: "pusher",
key: process.env.PUSHER_APP_KEY,
cluster: process.env.PUSHER_APP_CLUSTER,
wsHost: window.location.hostname,
wsPort: 6001,
wssPort: 6001,
encrypted: false,
forceTLS: false,
enabledTransports: ["ws", "wss"],
disableStats: true,
authEndpoint: process.env.API + "/broadcasting/auth",
auth: {
headers: {
accept: "application/json",
},
},
});
const channel = window.Echo.private("private.test.1");
channel
.subscribed(() => {
console.log("subscribed");
})
.listen(".test", (e) => {
console.log(e);
Notify.create({
message: e.message,
type: "positive",
});
});
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes(['middleware' => ['auth:sanctum']]);
require base_path('routes/channels.php');
}
}
Broadcast::channel('private.test.{id}', function ($id) {
return true;
});
public function broadcastOn()
{
return new PrivateChannel('private.test.1');
}
public function broadcastAs()
{
return 'test';
}
public function broadcastWith()
{
$faker = \Faker\Factory::create();
return [
'message' => $faker->sentence,
];
}
class TestEventCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'test:event';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
event(new TestEvent());
return Command::SUCCESS;
}
}
I have authenticated the user in the vue app
import { defineStore } from "pinia";
import { Cookies, Loading, Notify } from "quasar";
import { api } from "src/boot/axios";
export const useAuthStore = defineStore("auth", {
state: () => ({
isAuthentictated: Cookies.has("user") ? true : false,
user: Cookies.has("user") ? Cookies.get("user") : null,
}),
actions: {
login(data) {
Loading.show();
// CSRF token
api
.get("/sanctum/csrf-cookie")
.then(() => {
// Login
api
.post("/login", data)
.then((response) => {
this.isAuthentictated = true;
// get user
api.get("/api/user").then((response) => {
// set user cookie 2hrs
Cookies.set("user", response.data, {
expires: "2h",
});
this.user = response.data;
Loading.hide();
Notify.create({
message: "You are now logged in",
type: "positive",
});
this.router.push({ name: "dashboard" });
});
})
.catch((error) => {
console.log(error);
Loading.hide();
});
})
.catch((error) => {
console.log(error);
Loading.hide();
});
},
logout() {
Loading.show();
api
.post("/logout")
.then((response) => {
this.isAuthentictated = false;
Cookies.remove("user");
Loading.hide();
Notify.create({
message: "You are now logged out",
type: "positive",
});
this.router.push({ name: "login" });
})
.catch((error) => {
Loading.hide();
});
},
},
});
possible scenarios/references I'm trying:
https://stackoverflow.com/questions/72551784/laravel-9-sanctum-login-with-fetch-api-not-working
https://github.com/beyondcode/laravel-websockets/issues/386
https://github.com/laravel/echo/issues/342
https://stackoverflow.com/questions/45658149/laravel-echo-pusher-authentication-fails-403
https://laracasts.com/discuss/channels/laravel/laravel-echo-server-with-sanctum
https://www.youtube.com/watch?v=rzZoswkfi90&list=PLSfH3ojgWsQosqpQUc28yP9jJZXrEylJY&index=51
https://gist.github.com/sdwru/2fc0cd414b12cabfb3b84d05bb693375
https://laracasts.com/discuss/channels/laravel/laravel-echo-websockets-private-channels
Any tips?
Please or to participate in this conversation.