Cannot broadcast client event (connection not subscribed to channel private-chat) - laravel

I am building a real-time chat application with Laravel and Nuxt (Front-end and Back-end separated) using Pusher and Laravel-Echo, I have configured Laravel with pusher and it works fine and can see my requests in the Pusher debug console, I also want to mention two things first, I handle my authentication using Laravel-JWT and Nuxt-Auth and second, it works with public channels but since I converted it to private, Nuxt client can not subscribe anymore.
Here is error image:
Here is my Laravel config:
.env
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=MY_APP_ID
PUSHER_APP_KEY=MY_APP_KEY
PUSHER_APP_SECRET=MY_APP_SECRET
PUSHER_APP_CLUSTER=eu
channels.php
Broadcast::channel('chat', function() {
return true;
});
ChatController.php
public function sendMessage(Request $req) {
$user = Auth::user();
broadcast(new ChatEvent($user, $req->message))
->toOthers();
}
ChatEvent.php
public function __construct(User $user, $message)
{
$this->user = $user;
$this->message = $message;
}
public function broadcastAs()
{
return 'chat-event';
}
public function broadcastOn()
{
return new PrivateChannel('chat');
}
EventServiceProvider
use App\Events\ChatEvent;
use App\Listeners\SendMessageNotification;
...
protected $listen = [
ChatEvent::class => [
SendMessageNotification::class,
],
];
composer.json
"require": {
...
"pusher/pusher-php-server": "^7.0",
"tymon/jwt-auth": "^1.0"
}
Here is my Nuxt config:
package.json
"dependencies": {
...
"#nuxtjs/auth-next": "5.0.0-1624817847.21691f1",
"nuxt": "^2.14.6",
"pusher-js": "^7.0.3",
},
"devDependencies": {
...
"#nuxtjs/laravel-echo": "^1.1.0",
}
nuxt.config.js
buildModules: [
...
'#nuxtjs/laravel-echo',
],
echo: {
broadcaster: 'pusher',
key: 'my-app-key',
cluster: 'eu',
forceTLS: true,
plugins: ['~/plugins/echo.js']
},
echo.js plugin
export default function ({ $echo }) {
console.log($echo)
}
chat page index.vue
mounted() {
this.$echo.private(`chat`)
.listen(`chat-event`, (e) => {
this.addNewMessage(e.user, e.message)
})
.listenForWhisper('typing', (e) => {
console.log(e.name);
})
},
watch: {
typeMessage() {
this.$echo.private(`chat`)
.whisper('typing', {
name: this.typeMessage
})
}
},
Here is my echo console log:
My attempts to fix the error
I tried to test the back-end with the postman, and the pusher debug console works fine:
I tried to change the channel to public, and it works fine.
Conclusion
I think the problem comes from the authentication part when trying to subscribe to a private channel, hope anyone can help!

You are clearly facing a connection issue between your client and your API because of authentication.
Try with basic config without forceTLS (it must be also disabled in pusher website under App settings section).
In addition, be sure that your echo instance is hitting your auth endpoint with the required headers. It's needed for private/presence channels.
In my case I'm using passport so auth must be done with Bearer token attached in the header.
broadcaster: 'pusher',
key: process.env.VUE_APP_PUSHER_KEY,
cluster: process.env.VUE_APP_PUSHER_CLUSTER,
authEndpoint: process.env.VUE_APP_API_URL + '/broadcasting/auth',
auth: {
headers: {
Authorization: 'Bearer ' + this.$auth.token()
}
}
EDIT: sorry for not clarifying enough.
Presence/private channels are allowed only for authenticated users. Since you are using JWT you need to provide an Authorization header with the Bearer access token that you get when the user logs in.
I'm not sure how nuxt handles user tokens, but the this.$auth.token() is just a reference to how I retrieve a user's token by using websanova vue auth. But you may handle and store the token at the user's end in many ways

Related

Laravel Sanctum & broadcasting with Pusher.js (401, 419 error)

For 4 days I have been trying to connect to a private channel. Combined all the possible properties that I could find, but nothing solved the problem. A deeper understanding of the issue is needed.
I am creating an application using Laravel Sanctum, Nuxt.js, Nuxt-auth.
I need to connect to a broadcasting private channel.
At first I tried to create a connection using the #nuxtjs/laravel-echo package.
After long attempts to configure the connection, I found that the PisherConnector instance is not even created if I set the authModule: true (Public channels connect perfectly). Having discovered that this package is not actively supported and the fact that I do not have full access to connection management, I decided to abandon it.
Then I tried to set a connection using Laravel-echo and then directly through Pusher. Nothing works, I get either a 401 or 419 error.
I have a lot of questions and no answers.
When do I need to use laravel-echo-server?
When do I need to use pusher/pusher-php-server?
In which case do I need to connect to broadcasting/auth, and in which to api/broadcasting/auth? My frontend runs on api/login, but I don't want to provide external access to my API.
I added Broadcast::routes(['middleware' => ['auth: sanctum']]) to my BroadcastServiceProvider and to routes/api.php too (for testing). I'm not sure here either. Broadcast::routes(['middleware' => ['auth: api']]) may be needed or leave the default Broadcast::routes()?
What are the correct settings for configs: config/cors.php, config/broadcasting.php, config/sanctum.php, config/auth.php? What key parameters can affect request validation?
Should I pass CSRF-TOKEN to headers? I have tried in different ways.
When do I need to set the encrypted:true option?
What middleware should be present in the Kernel and what might get in the way?
If I set authEndpoint to api/broadcasting/auth I get 419 error (trying to pass csrf-token does not help). If I set authEndpoint to broadcasting/auth I get 401 error.
I do not provide examples of my code, since I tried all the options in all configs. I also tried to learn the documentation for my issue on the Laravel site, Pusher.js, looked at examples. Various examples mention some options but not others, and vice versa.
I would be glad to any advice.
I had the same issue as you. I had forgot to add the BroadCastingServiceProvider in my /config/app.php.
The app/Providers/BroadcastServiceProvider.php now looks like this:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes(['middleware' => ['api', 'auth:sanctum']]);
require base_path('routes/channels.php');
}
}
I have not added Broadcast::routes to any other route file.
This is what my Laravel Echo init looks like:
new Echo({
broadcaster: 'pusher',
key: 'korvrolf',
wsHost: window.location.hostname, // I host my own web socket server atm
wsPort: 6001, // I host my own web socket server atm
forceTLS: false,
auth: {
withCredentials: true,
headers: {
'X-CSRF-TOKEN': window.Laravel.csrfToken
}
}
})
Without the CSRF-token the endpoint will return the 419 status response.
In my index.blade.php for my SPA I print out the CSRF-token:
<script>
window.Larvel = {
csrfToken: "{{ csrf_token() }}"
}
</script>
Now, the /broadcast/auth endpoint returns a 200 response.
I had the same issue with Laravel & pusher.
I have fixed using decodeURIComponent and moving BroadCast::routes(['middleware' => ['auth:sanctum']]) to routes/api.php from BroadcastServiceProvider.php
Also, add withCredentials = true.
const value = `; ${document.cookie}`
const parts = value.split(`; XSRF-TOKEN=`)
const xsrfToken = parts.pop().split(';').shift()
Pusher.Runtime.createXHR = function () {
const xhr = new XMLHttpRequest()
xhr.withCredentials = true
return xhr
}
const pusher = new Pusher(`${process.env.REACT_APP_PUSHER_APP_KEY}`,
{
cluster: 'us3',
authEndpoint: `${process.env.REACT_APP_ORIG_URL}/grooming/broadcasting/auth`,
auth: {
headers: {
Accept: 'application/json, text/plain, */*',
'X-Requested-With': 'XMLHttpRequest',
'X-XSRF-TOKEN': decodeURIComponent(xsrfToken)
}
}
})
I hope this helps a bit.

Laravel Vue SPA using Sanctum response Unauthorized

The Sanctum Auth system on my local machine works well and I have no errors. But my deployed app is having trouble with authorizing a user. When I login it sends a request to get the user data then redirects. After auth completes you are redirected and the app make a GET request for more data. This GET route is guarded using laravel sanctum. But the backend is not registering that the user has made a successful login attempt so it sends a 401 Unauthorized error. Here is some code...
loadUser action from store.js
actions: {
async loadUser({ commit, dispatch }) {
if (isLoggedIn()) {
try {
const user = (await Axios.get('/user')).data;
commit('setUser', user);
commit('setLoggedIn', true);
} catch (error) {
dispatch('logout');
}
}
},
}
Route Guard on the routs.js checking to see isLoggedIn (which is just a boolean store locally)
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// if (to.meta.requiresAuth) {
if (isLoggedIn()) {
next();
} else {
next({
name: 'home'
});
}
} else {
next();
}
})
It was pointed out that I had forgotten the withCredetials setting for axios in bootstrap.js. I made this addition but my issue still remains.
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios.defaults.withCredentials = true;
Route middleware guard on the server side (this is where the request is getting turned away)
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('trucks', 'Api\TruckController');
});
In the laravel cors.php config file I changed the "supports_credentials" from false to true
'supports_credentials' => true,
It seems to me that the cookie information is not being over the api call (but I'm really not sure). This setup is working on my local machine but not on the server that I have deployed to.
Needed to add an environment variable to the .env file for SANCTUM_STATEFUL_DOMAINS and made that equal the domain name.
In the laravel sanctum.php config file...
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1')),

No response data from Laravel API using Axios

I am setting up authentication using Laravel (Laravel Framework version 5.8.4) as a REST API, but when I make a post request using Axios, I get back an empty string.
Here is my code in Laravel: "login" endpoint in my main controller:
class MainController extends Controller
{
public function login(Request $request){
$data = [
'message' => 'yo'
];
return Response::json($data, 200);
}
}
Here is my Axios code (from Vue.js method):
methods: {
submitRegistration: function() {
axios.post('http://envelope-api.test/api/auth/login', {
name: this.form.name,
email: this.form.email,
password: this.form.password,
password_confirmation: this.form.password_confirmation
})
.then(function (response) {
console.log("here's the response")
console.log(response);
})
.catch(function (error) {
console.log(error);
});
},
}
Here is the response from Postman (it works!)
{
"message": "yo"
}
Here is the response from my axios request in console (empty string, where's the data?) :
{data: "", status: 200, statusText: "OK", headers: {…}, config: {…}, …}
To get data from axios you should use response.data, not just response.
Edit: Try to respond with the helper.
response()->json($data);
I've got this figured out. Thanks for your help.
I had this chrome extension installed to allow CORS (Cross Origin Resource Sharing) so I could do API requests from localhost (apparently, not needed for Postman?).
I turned it off and installed it locally on Laravel using this post (answer from naabster)
After I installed this way, it worked regularly.

Pusher with Vue and Laravel API not working

I am using Vue SPA and Laravel. I have google it for hours and tried many things but I can't find a way to make it work.
In index.html I have
<meta name="csrf-token" content="{{ csrf_token() }}">
This is my subscribe method:
subscribe() {
let pusher = new Pusher('key', {
cluster: 'ap1',
encrypted: true,
authEndpoint: 'https://api_url/broadcasting/auth',
auth: {
headers: {
'X-CSRF-Token': document.head.querySelector(
'meta[name="csrf-token"]'
)
}
}
})
let channel = pusher.subscribe(
'private-user.login.' + this.user.company_id
)
channel.bind('UserLogin', data => {
console.log(data)
})
}
I am getting a 419 error saying: "expired due to inactivity. Please refresh and try again."
If you didn't noticed there I am trying to listen to a private channel.
419 means you don't pass the CSRF token verification. To solve the issue, there are some way.
You should pass the CSRF token to the Pusher instance. You can follow the instruction here https://pusher.com/docs/authenticating_users. I'll give you an example.
let pusher = new Pusher('app_key', {
cluster: 'ap1',
encrypted: true,
authEndpoint: 'https://api_url/broadcasting/auth',
auth: {
headers: {
// I assume you have meta named `csrf-token`.
'X-CSRF-Token': document.head.querySelector('meta[name="csrf-token"]')
}
}
});
Disable CSRF verification on the auth broadcasting route. But, this is not recommended, since CSRF verification here is important.
App\Http\Middleware\VerifyCsrfToken
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'broadcasting/auth'
];
Use laravel-echo, it's behind the scene use axios, you just need to pass CSRF token to the axios header.
// I assume you have meta named `csrf-token`.
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
}
hope that answer.
I found the solution I hope it can help others:
In front end:
let pusher = new Pusher('app_key', {
cluster: 'ap1',
encrypted: true,
authEndpoint: 'https://api_url/broadcasting/auth',
auth: {
headers: {
Authorization: 'Bearer ' + token_here
}
}
})
let channel = pusher.subscribe(
'private-channel.' + this.user.id
)
channel.bind('event-name', data => {
console.log(data)
})
As you can see above no need to use csrf token, instead use the jwt token.
In the backend, go to BroadcastServiceProvider and change this:
Broadcast::routes(); to Broadcast::routes(['middleware' => ['auth:api']]);

Laravel Echo Server can not be authenticated, got HTTP status 403

I got laravel-echo-server and Laravel 5 application with vuejs, and I'm trying to connect front end to back end via sockets.
I've managed to connect everything together via Echo.channel() method, however it will not subscribe with Echo.private()
This is what I got so far :
Once the page loads I call :
I initilise the Echo via
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001',
csrfToken : Laravel.csrfToken,
auth : {
headers : {
Authorization : "Bearer b6f96a6e99e90dzzzzzzz"
}
}
});
Then I create a new event via vue resourse
Vue.http.get('/api/errors/get');
This fires laravel event(new \App\Events\ErrorsEvent()); event
Which Broadcasts the event privately via
public function broadcastOn()
{
return new PrivateChannel('errors');
}
At this point laravel-echo-server responds with
Channel: private-errors
Event: App\Events\ErrorsEvent
CHANNEL private-errors
Then i try to subscribe to the channel with echo by running
Echo.private('errors')
.listen('ErrorsEvent', (e) => {
this.errors = e.errors;
console.log(e);
});
At which laravel-echo-server responds with
[14:47:31] - xxxxxxxxxxxxxx could not be authenticated to private-errors
Client can not be authenticated, got HTTP status 403
Does anybody know if I'm missing something here?
EDIT
This is my BroadcastServiceProvider
public function boot(Request $request)
{
Broadcast::routes();
/*
* Authenticate the user's personal channel...
*/
Broadcast::channel('App.User.*', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
}
Also, I've discovered that if I try to subscribe to
Echo.private('App.User.2')
.listen('ErrorsEvent', (e) => {
this.errors = e.errors;
console.log(e);
});
It connects and everything is ok, however it still refuses to connect with the errors channel
My issue was that I hadn't noticed there are two BroadcastServiceProvider::class entries in app/config.php
I had only checked the first one. Once I uncommented App\Providers\BroadcastServiceProvider::class I didn't need to specify the bearer token.
I believe the reason you couldn't connect on the errors channel in your above config is that you need to change your call in your BroadcastServiceProvider (or routes/channels.php) to
Broadcast::channel('errors', function ($user) {
return true; //replace with suitable auth check here
});

Resources