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

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.

Related

Laravel passport 404, Trying to connect a front end client to Laravel back-end api

Please help explain me a bit. I'm very new to this back-end, OAuth 2.0. I'm trying to make my Laravel project to become an api with Passport authorization system. But after following the passport installation guide from official documents and watching youtube tutorials, I still can't connect Laravel to my front end. Can you please point out what I'm missing ? Sorry if this question is super vague, I don't really understand much what's going on.
How and where do I create a new client for my passport back end api ? Should this code be in my front end (or in laravel?) ?
const data = {
name: 'Client Name',
redirect: 'http://example.com/callback'
};
axios.post('/oauth/clients', data)
.then(response => {
console.log(response.data);
})
.catch (response => {
// List errors on response...
});
In the official documents which talks about issuing access tokens with JSON api, there's this explanation.
However, you will need to pair Passport's JSON API with your own frontend to provide a dashboard for your users to manage their clients. Below, we'll review all of the API endpoints for managing clients. For convenience, we'll use Axios to demonstrate making HTTP requests to the endpoints.
The JSON API is guarded by the web and auth middleware; therefore, it may only be called from your own application. It is not able to be called from an external source.
It didn't explain how I could pair my frontend to this Laravel app. I tried adding this to my front end javascript.
const BASE_URL = 'http://127.0.0.1:8000/api';
**
* Trying Passport JSON api to get all clients for a given user.
*/
const getClients = async () => {
try {
const response = await axios.get(`${BASE_URL}/oauth/clients`);
const clients = response.data;
console.log('Here are your clients', clients);
} catch (errors) {
console.error(errors);
}
}
/**
* Trying to create a new client. Not sure if this is equivalent to linking this external app to the backend api or not
*/
const addClient = async() => {
const data = {
name: 'front end jajaja',
redirect: 'http://www.google.com'
};
const response = await axios.post(`${BASE_URL}/oauth/clients`, data);
console.log(response.data);
}
/**
* main function to test it all
*/
const main = () => {
addClient();
// getClients();
}
main();
but after I ran npm run dev and the main() method is called from the front end, console displayed an error saying
POST http://127.0.0.1:8000/api/oauth/clients 404 (Not Found)
I haven't edit routes/api.php.
<?php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
And from a tutorial by Andrew, he said he used Laravel breeze to make a mock client, but can I use my super simple front end JavaScript instead ? Can you please explain me, how can I connect my front to the back ? How can I get the authorization code, client secret ??

Laravel Echo not subscribing to Pusher Presence Channel, not even in Pusher dashboard

I have spend many hours to solve this issue of mine, reading the doc multiple times, googling here and there: SO, Laracast, Larachat, etc, but still couldn't get Laravel Echo to subscribe to Pusher presence channel, and it doesn't show any error in console tab
Public and Private channel are working fine and smooth, users can subscribe, users can listen / trigger event(s)
Note: before creation of this post, I have search questions related to my current issue, none of them have answer
Some questions similar to mine:
https://laravelquestions.com/2020/12/15/laravel-echo-not-joining-presence-channel-in-production/
Laravel Echo + Laravel Passport auth in private / presence websockets channels
https://laravel.io/forum/facing-issues-upon-subscribing-to-presence-channel
etc..
Spec:
Laravel: 7.30.1
laravel-Echo: 1.10.0 (latest; atm)
pusher/pusher-php-server: 4.0
pusher-js: 7.0.3 (latest; atm)
In resource/js/bootstrap.js
import Echo from 'laravel-echo'
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true,
authEndpoint: '/api/broadcasting/auth',
auth: {
headers: {
'Authorization': `Bearer ${localStorage['token']}`
}
}
});
In routes/api.php
// https://stackoverflow.com/questions/55555844/authorizing-broadcasting-channel-in-an-spa
Route::post('/broadcasting/auth', function (Request $request) {
$pusher = new Pusher\Pusher(
env('PUSHER_APP_KEY'),
env('PUSHER_APP_SECRET'),
env('PUSHER_APP_ID'),
[
'cluster' => env('PUSHER_APP_CLUSTER')
]
);
// This will return JSON response: {auth:"__KEY__"}, see comment below
// https://pusher.com/docs/channels/server_api/authenticating-users
$response = $pusher->socket_auth($request->request->get('channel_name'), $request->request->get('socket_id'));
return $response;
})->middleware('auth:sanctum');
In routes/channels.php
// https://laravel.com/docs/8.x/broadcasting#authorizing-presence-channels
Broadcast::channel('whatever', function ($user) {
return [
'id' => $user->id,
'name' => $user->name
];
});
In home.vue
...
...
created() {
Echo.join('whatever') // DOES NOT WORK, Even in mounted() vue lifehook, and in Pusher dashboard, it doesn't show this channel name
.here((users) => {
console.table(users)
})
}
Q: Why Laravel Echo not subscribing to Pusher presence channel? and even in Pusher, it doesn't show channel name: presence-whatever, only disconnected (after I refreshed the page) and then connected like nothing happen
Thanks in advance
1. Make sure to set the broadcast driver to .env after that do php artisan config:clear
BROADCAST_DRIVER=pusher
2. Ferify your auth.php. I have like this
3. Check your broadcasting.php this is my dev config
4. Check CORS settings I installed fruitcake/laravel-cors and this is my cors.php config
Maybe for you supports_credentials need to be true
5. Check your channels.php. I have
Make sure to set the broadcast driver to env after that do php artisan config:clear
Use window.Echo.channel('whatever') to join the channel.
Had the exact same issue and was stuck for hours.
I changed 'useTLS' to FALSE in config/broadcasting.php and magically got it working.

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']]);

Nuxt Axios Dynamic url

I manage to learn nuxt by using following tutorial
https://scotch.io/tutorials/implementing-authentication-in-nuxtjs-app
In the tutorial, it show that
axios: {
baseURL: 'http://127.0.0.1:3000/api'
},
it is point to localhost, it is not a problem for my development,
but when come to deployment, how do I change the URL based on the browser URL,
if the system use in LAN, it will be 192.168.8.1:3000/api
if the system use at outside, it will be example.com:3000/api
On the other hand, Currently i using adonuxt (adonis + nuxt), both listen on same port (3000).
In future, I might separate it to server(3333) and client(3000)
Therefore the api links will be
localhost:3333/api
192.168.8.1:3333/api
example.com:3333/api
How do I achieve dynamic api url based on browser and switch port?
You don't need baseURL in nuxt.config.js.
Create a plugins/axios.js file first (Look here) and write like this.
export default function({ $axios }) {
if (process.client) {
const protocol = window.location.protocol
const hostname = window.location.hostname
const port = 8000
const url = `${protocol}//${hostname}:${port}`
$axios.defaults.baseURL = url
}
A late contribution, but this question and answers were helpful for getting to this more concise approach. I've tested it for localhost and deploying to a branch url at Netlify. Tested only with Windows Chrome.
In client mode, windows.location.origin contains what we need for the baseURL.
# /plugins/axios-host.js
export default function ({$axios}) {
if (process.client) {
$axios.defaults.baseURL = window.location.origin
}
}
Add the plugin to nuxt.config.js.
# /nuxt.config.js
...
plugins: [
...,
"~/plugins/axios-host.js",
],
...
This question is a year and a half old now, but I wanted to answer the second part for anyone that would find it helpful, which is doing it on the server-side.
I stored a reference to the server URL that I wanted to call as a Cookie so that the server can determine which URL to use as well. I use cookie-universal-nuxt and just do something simple like $cookies.set('api-server', 'some-server') and then pull the cookie value with $cookies.get('api-server') .. map that cookie value to a URL then you can do something like this using an Axios interceptor:
// plguins/axios.js
const axiosPlugin = ({ store, app: { $axios, $cookies } }) => {
$axios.onRequest ((config) => {
const server = $cookies.get('api-server')
if (server && server === 'some-server') {
config.baseURL = 'https://some-server.com'
}
return config
})
}
Of course you could also store the URL in the cookie itself, but it's probably best to have a whitelist of allowed URLs.
Don't forget to enable the plugin as well.
// nuxt.config.js
plugins: [
'~/plugins/axios',
This covers both the client-side and server-side since the cookie is "universal"

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