laravel reactjs pusher: presence channel response is 302 - reactjs

I'm trying to make a reactjs application where an user can only login to one device at the time with the same user credentials. Unfortunately it isn't working.
I'm trying to authenticate a presence channel with reactjs to laravel but I get a 302 response.
reactjs:
Pusher.logToConsole = true;
var pusher = new Pusher("9028d58568392772df59", {
cluster: "eu",
forceTLS: true,
authEndpoint: '/broadcasting/auth',
auth: {
headers: {
'X-CSRF-Token': csrf_token
}
}
});
var channel = pusher.subscribe("presence-HandleCredentials");
channel.bind("sameCredentials", function(data) {
console.log(data);
alert(JSON.stringify(data));
});
channel:
Broadcast::channel('App.User', function ($user, $id = 1) {
return (int) $user->id === (int) $id;
});
broadcast:
public function boot()
{
Broadcast::routes(['middleware' => ['auth:web']]);
require base_path('routes/channels.php');
}
When I added this ['middleware' => ['auth:web']] I got the error. Before I added that I got a 403 error.
in the config\app.php I uncommented App\Providers\BroadcastServiceProvider::class,
Are there any tutorials out there that are build with laravel and reactjs for a presence channel?
does anyone know how to get past this 302 redirect?

Recently had the same issue with my laravel-websockets and laravel echo.
In my case I was unable to solve the 302, as Broadcast was unable to authenticate my logged in user. I was trying to subscribe to my private channel. So the workaround i found was that i manually created a POST route in web.php as "/broadcasting/auth". This is what my front-end requests to. So The updated code in web.php is as follows.
Route::post('/broadcasting/auth', function(Request $request){
$pusher = new Pusher\Pusher(
env('PUSHER_APP_KEY'),
env('PUSHER_APP_SECRET'),
env('PUSHER_APP_ID'),
array(
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => false,
'host' => env('APP_URL'),
'port' => 6001,
'scheme' => 'http',
)
);
return $pusher->socket_auth($request->request->get('channel_name'),$request->request->get('socket_id'));
});
I was creating my own websocket that is why i had to mention the host & port within the options, you don't need to use it if you are Using Pusher. You can also add other middlewares to the routes if needed.
You have to comment out the following line in app/providers/BroadcastServiceProvider:
public function boot()
{
// Broadcast::routes();
require base_path('routes/channels.php');
}
so that the request can reach my broadcasting/auth route in web.php.
Try this. now this should return a 200 when the broadcasting/auth is requested by your client end with response of an auth code. Do let me know if this solves your problem.

Related

Laravel Echo Authorizer

im using Laravel 8 as api backend and react js in front.
im trying to build a websocket connection with react and laravel using laravel-echo-server, laravel-echo and redis.
the problem is im not using laravel guard auth ( the auth process is written by my self for some reasons and handled with middlewares not auth:api guard ) and so i can't using laravel guard for authenticate the user in private channels.
beacuse of that im looking for other ways to authenticate users in private channels without laravel guard.
i tried laravel-echo authorizer :
window.io = socketio;
window.Echo = new Echo({
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
axios.post('/api/auth/custom', {
socket_id: socketId,
channel_name: channel.name
}).then(response => {
callback(false, response.data);
}).catch(error => {
callback(true, error);
});
}
};
},
broadcaster: 'socket.io',
host: `${host}:${port}`,
transports: ['websocket'],
});
but nothing happend. actually no request is sent to the '/api/auth/custom'
at the end, Channels are working but Private Channels need authentication...
any solutions ?

read cookies in front (Reactjs) that comes from nodejs

i need to get cookies(it's a token) which has been defined in a node js Route file to my front, because i need to check infos of this token to show data if it's a user or admin.
THis is some code of the cookies :
// auth with google+
router.get('/auth/google', passport.authenticate('google', {
scope: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email'
]
}));
// callback route for google to redirect to
// hand control to passport to use code to grab profile info
router.get('/auth/google/callback*', passport.authenticate('google'), (req, res) => {
if(req.user){
console.log(req.user);
res.cookie('token', req.user);
return res.redirect(config.clientURL);
}
else{
console.log('error');
return res.redirect(config.clientURL);
}
});
// auth with faceboook
router.get('/auth/facebook', passport.authenticate('facebook'));
// callback route for facebook to redirect to
// hand control to passport to use code to grab profile info
router.get('/auth/facebook/callback*', passport.authenticate('facebook'), (req, res) => {
console.log("je suis dans la route callback");
if(req.user){
console.log(req.user);
res.cookie('token', req.user);
return res.redirect(config.clientURL);
}
else{
console.log('error');
return res.redirect(config.clientURL);
}
});
Edit :
i did this :
const auth_head = document.cookie.split('.')[0];
const auth_payload = document.cookie.split('.')[1];
const auth_signature = document.cookie.split('.')[2];
var auth_token = auth_head + "." + auth_payload + "." + auth_signature;
console.log(JSON.parse( auth_head));
console.log(JSON.parse( auth_payload));
console.log(JSON.parse( auth_signature));
but i got this error :
Uncaught (in promise) SyntaxError: Unexpected token o in JSON at position 1
Thank you
As I mentioned in the comments, it's good advice to use httpOnly flag when setting cookies; this means that you need another strategy to return the user data.
Option 1: One easier to implement way could be: After your server redirects the client to let's say /logged-in, you can fetch the user data from let's say /api/userinfo; the response should a json containing the user info; you should use that json to store the information in your client using localStorate.setItem(...). This is the classic and more used way to store your user data in the client.
Example Server (Create an endpoint that returns the logged-in user info):
// Server endpoint that returns user info
router.get('/api/userinfo',
passport.authenticate(your_strategy_here),
(req, res) => {
res.json({ name: req.user.name, role: req.user.role }); // Return just what you need
})
Example Client (Create a component that requests the user info from the new server endpoint):
componentDidMount(){
fetch('/api/userinfo')
.then( res => res.json() )
.then( user => localStorate.setItem('user', user);
}
Option 2: Give Google a URL which is resolved by the client, and then have the client send the request to /auth/facebook/callback; then have the server do res.json(user), instead of the doing the redirect.
Google -> /your-client-app/auth/callback
Client -> /auth/facebook/callback
Option 2 is my advice, however, Option 1 may be more straight forward for your current setup.
Option 3: Disable httpOnly when setting the cookie, there are security concerns when doing this and it's not meant to be done like that in production apps.
res.cookie('token', req.user, { httpOnly: false });
And then on your client, you can use the following data to check the cookies.
const cookieData = document.cookie;
console.log(cookieData)

Trigger an event to private channel in react app

I want to trigger an event to pusher private channel and my server side language is laravel I reviewed a lot of resources, but I did not find a comprehensive approach which covers both the server side and the front side Finally I got this solution
in the first step :
export const SendChat = () => {
try {
var pusher = new Pusher('YOUR_APP_KEY', {
cluster: 'ap2',
forceTLS: true,
authTransport: 'jsonp',
authEndpoint: `${baseUrl}pusher/auth`,
});
var channel = pusher.subscribe('private-channel');
channel.bind('pusher:subscription_succeeded', function() {
var triggered = channel.trigger('client-EVENT_NAME', { 'message': 'Hi ....' });
console.log(triggered)
});
} catch (error) {
console.error(error);
}
}
and call it somewhere
<Button onClick={this.props.SendChat} waves='light' >Send</Button>
you must Enable client events in pusher account setting
login to your pusher account -> select the channel ->App Settings -> select Enable client events -> update
add your app key, channel name and event name after that we need authorization in server side this is sample laravel code first add this route in web.php
Route::get('pusher/auth', 'PusherController#pusherAuth');
make PusherController.php like this :
public function pusherAuth()
{
$user = auth()->user();
if ($user) {
$pusher = new Pusher('auth_key', 'secret', 'app_id');
$auth= $pusher->socket_auth(Input::get('channel_name'), Input::get('socket_id'));
$callback = str_replace('\\', '', $_GET['callback']);
header('Content-Type: application/javascript');
echo($callback . '(' . $auth . ');');
return;
}else {
header('', true, 403);
echo "Forbidden";
return;
}
}
test it you should see something like this
Pusher : State changed : connecting -> connected with new socket ID 3953.****556
Pusher : Event sent : {"event":"pusher:subscribe","data":{"auth":"83045ed1350e63c912f5:328fb78165d01f7d6ef3bb6d4a30e07c9c0ad0283751fc2c34d484d4fd744be2","channel":"private-chat"}}
Pusher : Event sent : {"event":"client-MessageSent","data":{"message":"Hi ...."},"channel":"private-chat"}
true
It doesn't matter much which client-side language you are using. Angular, Vue, React they all are JS framework and libraries. And, you can consider using a generic JS code which you can place in all 3 apps.
Let me try to give you a detailed answer I can give as per my knowledge.
In order to get started, you should first complete try to complete Chat scenario without pusher. i.e: user should be able to send a message from front-end via the API and it should be stored inside the database.
Once you have done this it is very easy to include pusher in the flow. ( In simple words, you'll have to broadcast an event and that'll inform the Socket Server to broadcast a message to all/other user(s) on the channel )
For Pusher Authentication, you don't need to explicitly create a route and a method. Once you have uncommented BroadcastServiceProvider inside config/app.php. You can run:
php artisan route:list
and, you'll see a route for broadcast broadcasting/auth.
You can use this route to authenticate. Although, you can make few changes and prepend /api before this.
Go into BroadcastServiceProvider.php and replace your boot method with:
public function boot()
{
Broadcast::routes(
[
'prefix' => 'api',
'as' => 'api.broadcasting.auth',
'middleware' => ['auth:sanctum'],
]
);
require base_path('routes/channels.php');
}
I assume you're using Laravel Sanctum for Authentication. If not you need to change the authentication middleware to your provider.
Once done, you can authenticate from frontend using this auth route. So, what I have done is created a service in ReactJS and in the constructor I have created a Pusher instance :
this.pusher = new Pusher(PUSHER_APP_KEY, {
authEndpoint: 'http:localhost:8000/api/broadcasting/auth',
cluster: PUSHER_CLUSTER,
useTLS: true,
auth: {
headers: {
Authorization: 'Bearer ' + authHeader
}
}
});
You only need to instantiate your Pusher once and use this instance throughout the app. So, that's why I have created a service class for Pusher.
If you want things to be simple for now you need to execute this code on the page where you will use pusher. Once the Page load, you need to call this code. So, you'll do:
let pusher = null;
useEffect(() => {
pusher = new Pusher(PUSHER_APP_KEY, {
authEndpoint: 'http:localhost:8000/api/broadcasting/auth',
cluster: PUSHER_CLUSTER,
useTLS: true,
auth: {
headers: {
Authorization: 'Bearer ' + authHeader
}
}
});
}, []);
So, this way we have an instance of Pusher in our functional component or page.
Now, we need to subscribe to channel.
Using this instance of pusher we can subscribe to channels. If you have followed the useEffect approach on the same page then, right after getting the instance you can subscribe to channels and bind to events using this code:
const channel = pusher.subscribe('private-chat.' + channelName)
And, to bind to an event you can do:
channel.bind('event.name', function(data) {
console.log(data);
});
Make sure to replace "channelName" and "event.name" with your channel and event name respectively.
Now you'll be able to listen to your event once broadcasted from the backend.
So, you'll do something like this from the backend. You'll have a method that will store the message inside the database so, let's say that code is:
public function sendMessage (Request $request){
//.... Rest of the logic
$user = $request->user();
// Store the message
$chatMessage = $chat->messages()->create([
'message' => $message,
'sender_id' => $user->id
]);
broadcast(new NewMessage($user, $chatMessage))->toOthers();
//... Rest of the logic
}
This broadcast message will send this message to other user in the chat.
I hope this answer gives you a good idea and direction.
For work with WebSockets via Pusher on Laravel, I recommended using the package Laravel Echo for React part. And on the backend side in config/broadcasting.php setup configuration for Pusher.
See more detail on official documentation Laravel how to use Pusher on the backend side and frontend side.
https://laravel.com/docs/8.x/broadcasting#pusher-channels

How to create Token-Based Authentication(oauth2) for AngularJS and Laravel Apps via passport

I created an web app which it uses laravel default registration(auth), I've tested passport oauth2 client access token from taylor tutorial. My web app uses angular js for UI and laravel for backend , so I need to create user, when create user request is sent from angular and then create a global access token to give it in my response to angular which then in all later request I use it to authenticate requests.
actually I want to implement oauth2 authentication for my web app, but so far I've searched a lot but I couldn't find any useful step by step tutorial for it.
anyone can help me out?
FYI: I'm using laravel 5.3 with passport enabled and angular js 1.5 for frontend.
Use JWT token based authentication here you can learn about jwt https://jwt.io/
I've solved this.
I've Customized laravel auth for login and register and created a method which will send a request to the server to create an access token for registering user or log in.
I've set up passport and test it as taylor did in his toturial.
then in AuthenticatesUsers.php I've changed sendloginResponse method response like :
protected function sendLoginResponse(Request $request)
{
isset($request->token) ? $token = $request->token : $token = null;//Check if login request contain token
$request->session()->regenerate();
$this->clearLoginAttempts($request);
return $this->authenticated($request, $this->guard()->user())
? $this->StatusCode($token,$this->credentials($request),$status=false) : $this->StatusCode($token,$this->credentials($request),$status=true);
}
And I have added this method to request access token and send it as json response :
public function StatusCode($token,$user,$status){
if( $token != null && $token != ''){
return ($status == true) ? response()->json(GetToken($user),200) : response()->json(['Message' => 'Failed to log in'],403);
}
function GetToken($userobject)
{
$http = new Client;
$response = $http->post('http://localhost/iranAd/public/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => '1',
'client_secret' => 'xKqNbzcXyjySg20nVuVLw5nk5PAMhFQOQwRTeTjd',
'username' => $userobject['email'],
'password' => $userobject['password'],
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
}
function RefreshToken($token,$userobject)
{
$http = new Client;
$response = $http->post('http://localhost/iranAd/public/oauth/token', [
'form_params' => [
'grant_type' => 'refresh_token',
'refresh_token' => 'refresh_token',
'client_id' => '1',
'client_secret' => 'xKqNbzcXyjySg20nVuVLw5nk5PAMhFQOQwRTeTjd',
'username' => $userobject['email'],
'password' => $userobject['password'],
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
}
return ($status == true) ? response()->json(GetToken($user),200) : response()->json(['Message' => 'Failed to log in'],403);
}
Same Procedure for register users.
The purpose of this post is not to answer(as already answered) but to give more info to other readers who eventually need more info on topic.
This is very helpfull tutorial just on this issue Implementing Vue.js 2.0 and Laravel 5.3 with CORS problem solution
Check this one and 2 next clips.
Some of this you can find in shorter form here here

Angular + Laravel Delete File

I'm building a REST-full web-app using Laravel 5.2 (for the back-end) and AngularJS for the font-end. I connect to the Laravel back-end by the use of an API. Now I have stumbled upon the following problem: I can properly upload an image, but deleting it again is not working.
The files are uploaded into the Larvel public/images/uploaded/ folder.
This is my Angular Service (the http request fired when clicking the 'delete' button) where the variable imageToDelete is the relative path to the image.. So Far so good, the request is firing and the imageToDelete variable is populated.
function deleteProfileImage(imageToDelete) {
return $http({
method: 'DELETE',
url: '/api/pictures/' + imageToDelete
})
.then(deleteProfileImageSuccess)
.catch(deleteProfileImageError);
function deleteProfileImageSuccess(response) {
$log.info('Deleting profile picture Success.');
console.log(response);
return response;
}
function deleteProfileImageError(error) {
$log.info('Deleting profile picture failed because: ' + error.data);
return error;
}
}
This angular HTTP request fires a DELETE request to the following function in my Laravel Controller.
public function destroy($imageToDelete)
{
if(Storage::delete($imageToDelete)) {
return response()->json(['success' => 'success', 'message' => 'File Deleted']);
} else {
return response()->json(['error' => 'Deleting Image failed.'])
->setStatusCode(Response::HTTP_BAD_REQUEST);
}
}
And here, the Storage::delete($imageToDelete) does nothing. It does not delete the file provided with the Angular DELETE request.
Some things I have already tried:
Working with File::delete() instead of Storage::delete()
Working with unlink() instead op the Laravel Facades;
Sending the imageToDelete as data with the HTTP DELETE request (so not in the URL).
But all without success.
How can I make Laravel (PHP) delete the image?
Thank you for helping!
You can try
$storage = Storage::find($imageToDelete);
if($storage->delete()) {
return response()->json(['success' => 'success', 'message' => 'File Deleted']);
}
or
if(Storage::destroy($imageToDelete)) {
return response()->json(['success' => 'success', 'message' => 'File Deleted']);
}
or
$deleteImage = Storage::where('id', $imageToDelete)->delete();
if($deleteImage){
return response()->json(['success' => 'success', 'message' => 'File Deleted']);
}
You might need to check your image path. When you use laravel storage or file facde your file path should be absolute path for example public_path($imageToDelete)

Resources