Satellizer OAuth Unlinking gives 404 error - angularjs

I'm testing satellizer example with Laravel back end for Facebook, once user links the Facebook account to the app its impossible to unlink it with satellizer, whenever user clicks Facebook Unilink button it gives 404 error,
http://localhost:8000/auth/unlink
404 Not found.
But on Laravel router.
Route::get('auth/unlink/{provider}', ['middleware' => 'auth', 'uses' => 'AuthController#unlink']);
Please help me to fix this bug.

Did you find the solution for this?
After check the below link I end up founding a solution for my issue.
https://github.com/sahat/satellizer/issues/269
Basically in the Satellizer documentation explain that provider and httpOptions was the parameters you can send via the unlink call, however, the options never gets passed as it only retrieves the information from the first parameter which is the provider.
You can see a bit more about the accepted parameters in the piece of code below that is inside of the satellizer.js file.
OAuth.prototype.unlink = function (provider, httpOptions) {
if (httpOptions === void 0) { httpOptions = {}; }
httpOptions.url = httpOptions.url ? httpOptions.url : joinUrl(this.SatellizerConfig.baseUrl, this.SatellizerConfig.unlinkUrl);
httpOptions.data = { provider: provider } || httpOptions.data;
httpOptions.method = httpOptions.method || 'POST';
httpOptions.withCredentials = httpOptions.withCredentials || this.SatellizerConfig.withCredentials;
return this.$http(httpOptions);
};
My not so elegant solution is as below:
html
<button class="btn btn-sm btn-danger float-left" ng-if="user.facebook"
ng-click="unlink({provider:'facebook', options: {'param1':'value','param2':'value2'}})"><i class="ion-social-facebook"></i> Unlink Facebook Account
</button>
I have basically wrapped the information I want to send by the variable provider (renamed to just data) in the JS code below.
//unlink a social login profile from user's profile
$scope.unlink = function (data) {
console.log(data);
$auth.unlink(data)
.then(function () {
toastr.info('You have unlinked a ' + data.provider + ' account');
$scope.getProfile();
})
.catch(function (response) {
toastr.error(response.data ? response.data.message : 'Could not unlink ' + data.provider + ' account', response.status);
});
The JSON sent via view does not look pretty but works:
provider Object
provider "facebook"
options Object
flag "unlink"
view "profile"
userId 236
Nothing of the above resolved the 404 issue but resolve the passing of parameters from the original satellizer unlink function.
The issue with the route happens because Laravel is blocking that route in the file Authenticate.php inside of the function "public function handle($request, Closure $next)"
You can ether route without the middware like this
Route::post('auth/unlink', 'AuthController#unlink');
Route::get('auth/unlink', 'AuthController#unlink');
Route::any('auth/unlink', 'AuthController#unlink');
The above will make sure the call will hit the controller one whay or another. how you are getting the parameters in the controller will depend if you choose post/get/any. Meaning you will retrieve the parameters via Laravel variable $request from
public function unlink(Request $request)
or using the Input facade like this
$input = Input::all();
in here you can do whatever you want with the variable values passed. Now is up to you on the handling.
Note: The satellizer code sets by default the method to POST if no method is passed in the httpOptions as you can see below:
OAuth.prototype.unlink = function (provider, httpOptions) {
if (httpOptions === void 0) { httpOptions = {}; }
httpOptions.url = httpOptions.url ? httpOptions.url : joinUrl(this.SatellizerConfig.baseUrl, this.SatellizerConfig.unlinkUrl);
httpOptions.data = { provider: provider } || httpOptions.data;
httpOptions.method = httpOptions.method || 'POST';
httpOptions.withCredentials = httpOptions.withCredentials || this.SatellizerConfig.withCredentials;
return this.$http(httpOptions);
};
That does not really helps when the code with the Laravel example comes with the route calling the get method not the post and in the js example no options of http is set to get. Meaning you are trying to call get where post is the default therefore the route will never work.
Sorry if I am not more clear as this is my first time trying to put my thinking here and English is not really my first language.
Good luck.

Related

using multiple parameters in get method from (axios) reactjs front-end to asp.net core webapi Back-end

I want to get shop by userId and userName if the shop for that user exists.
i have used the url in axios get call as follows:
const url = `${config.apiUrl}/api/Shops/`
useEffect(() => {
axios.get(url + `details?id=${user.id}&shopuser=${user.username}`)
.then((res) => {
setShop(res.data);
})
.catch((error)=>{
console.log(error);
}
)
At the back-end asp.net core web api I have used the following action Method:
[HttpGet("{dashboard}")]
public async Task<ActionResult<Shop>> Dashboard(int id,string shopuser)
{
var shop = await _context.Shops.FirstOrDefaultAsync(a=>a.UserId==id && a.UserName==shopuser);
if (shop == null)
{
return NotFound();
}
return shop;
}
The problem is that the call is not sent to this action method with correct parameters and hence the response with error error 500 .
The controller name is as follows:
{
[Route("api/[controller]")]
[ApiController]
public class ShopsController : ControllerBase
{
.......
....
}
}
I have checked the Network tab of console the parameters are sent as follows:
/api/Shops/details?id=1&shopuser=asifranjha
thanks in advance for help.
[HttpGet("{dashboard}")] annotation means that your get request is expecting a route parameter (dashboard).So if you want to trigger your method, you should call /api/Shops/1?shopuser=asifranjha (I recommend you to keep the same naming so you should change dashboard to id or the opposite, you could also provide the type for your route with route constraints).
If you want to use /api/Shops/details?id=1&shopuser=asifranjha, you should just change your annotation parameter so it would look like this: [HttpGet("details")] and everything should work.Good luck with your project!

Axios - receiving a 404 error when trying to delete user from database

so I'm currently trying to delete a user from a mySql database on clicking a delete button. The app is Laravel backend, React front end.
When I click the delete button, it returns a 404 error 'DELETE http://localhost:8000/delete/5 404 (Not Found)'
onDelete method:
onDelete(user_id) {
axios.delete('http://localhost:8000/users/delete/' + user_id)
.then((response) => {
})
}
UserController
public function destroy($id)
{
$user = User::find($id);
$user->delete();
}
Button in code:
<button onClick={this.onDelete.bind(this, Users.id)}>Delete</button>`
Web Route:
Route::delete('users/delete/{id}', 'Api\UserController#destroy');
The error contains the correct id (the last number in the error code url is the correct id of the user I'm trying to delete) but I've got no idea why it's giving me a 404 error.
Update Turns out the delete route isn't in my route list. (php artisan route:list)
Route::get('userList', 'Api\UserController#index');
Route::post('users/store', 'Api\UserController#store');
Route::delete('users/delete/{id}', 'Api\UserController#destroy');
Strange because the get and post routes are in the route list, but not my delete route. (The above code is in my web.php file) All three methods (index, store and destroy) are inside my UserController controller:
public function index()
{
$result = User::all();
return $result;
}
public function store(Request $request)
{
$user = new User();
$user->name = $request->user_name;
$user->email = $request->user_email;
$user->phone_number = $request->user_phoneNumber;
$user->account_type = $request->user_accType;
$user->qualifications = $request->user_qualifications;
$user->save();
}
public function destroy($id)
{
$user = User::find($id);
$user->delete();
}
The first is to understand whats the error code is saying, according to this 404 Not Found client error response code indicates that the server can't find the requested resource, first of try to clean this in a way
onDelete(user_id) {
url = `http://localhost:8000/users/delete/${user_id}`;
//here make sure url is fine with Id
axios.delete(url)
.then((response) => {
})
}
after this you have to check in the network call, either the request is going fine to the server with id or not. then lastly double check api is expecting the delete call or its expecting the get call with id for deletion, because many backend guys delete records by using get calls. ping me back if still is not fixed.

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 redirect url from angularjs login submission via laravel 5

i'm developing my website with angularjs and laravel5. i wrote code for login and registration page in both angularjs and laravel5 where validate my value and insert everything works good but redirect url in laravel 5 not occur .
i wrote code like return redirect('Home/profile') in login controller. it returns total page to angularjs controller not redirecting page.
routes.php in laravel:
Route::group(array('prefix'=>'api'),function(){
Route::resource('register','Registration\RegisterController#basicForm');
Route::resource('login','Registration\RegisterController#makeLogin');
});
my controller :
public function makeLogin()
{
$email=Input::get('email');
$pwd=Input::get('pwd');
$verify=Authenticated::attempt($email,$pwd);
if($verify)
{
return redirect('Home/profile');
}
else if($verify=='user')
{
return redirect('/')->with('Email address mismatch');
}
else if($verify=='pwd')
{
return redirect('/')->with('Password Authentication Failed');
}
}
i send post request from angular js controller via factory method:
this.scope.authUserInfo.authenticateUser(this.scope.signin).then(function(data){
console.log(data.data);
});
In this console.log,display 'Home/profile' page
I can see many mistakes in the Laravel code.
What is Authenticated in your code? The authentication service in Laravel is called Auth. The way you use the attempt() method is not correct; this is the method signature:
attempt(array $credentials = array(), bool $remember = false, bool $login = true)
so you should pass the email and the password in the first parameter as an array, something like:
$verify = Auth::attempt(['email' => $email, 'password' =>
Moreover, the attempt() method returns a boolean: true on success and false on failure. Your code
if ($verify) {
// ...
} else if ($verify == 'user') {
// ...
} else if ($verify == 'pwd') {
// ...
}
has no sense and you never run the else parts because on failure false is always different from either 'user' or 'pwd'. So, when the authentication fails you reach the end of the makeLogin() method and Laravel returns a blank page.
You should use something like:
if ($verify) {
// authenticated: go to the profile page
} else {
// username OR password are wrong
}
(In my opinion you shouldn't give hints on what of the two is wrong for security reasons: a potential attacker would know if he/she guessed a right email and concentrate the attempts on guessing the password.)
If you really want to give a hint to the user on what was wrong with her data, you should use a different technique, like searching the users table for a record with the right email to know whether the user exists (the provided password was wrong) or not (the provided email was wrong).
On the client side, I don't think Angular will redirect on your own. See answer to Handle an express redirect from Angular POST, even if in that question the server uses ExpressJs and not Laravel, but the basics are the same.
You should understand that in most cases an Angular client expect to receive only data and not a full HTML page. Here is my little attempt to do what you want:
On the Laravel side:
public function makeLogin(Request $request)
{
$email = $request->get('email');
$pwd = $request->get('pwd');
if (Auth::attempt(['email' => $email, 'password' => $pwd])) {
// authenticated!
// if you return an array from a controller public method, Laravel
// will convert it to JSON; I also use the url() Laravel helper to
// generate a fully qualified url to the path
return [
'status' => 'redirect',
'to' => url('home/profile')
];
}
// failed
return [
'status' => 'failed',
'message' => 'Email or password are wrong'
];
}
Now the method will return a JSON answer. On the Angular side, you can do something like (I'm not an Angular guru, there could be mistakes here):
this.scope.authUserInfo.authenticateUser(this.scope.signin).then(function(data) {
if (data.response == 'redirect') {
$location.url(data.to);
} else {
// failed: you can show the error message
console.log(data.message);
}
});
UPDATE
I noticed there is something wrong in the routes too. Your controller does not seems a resourceful controller, so don't use the Route::resource() method. Use the get() and post() methods instead:
Route::group(array('prefix'=>'api'),function(){
Route::get('register', 'Registration\RegisterController#basicForm');
Route::post('login', 'Registration\RegisterController#makeLogin');
});
so that you can give the method that should be called in your controller.

How to redirect to CodeIgniter controller using angular js

I am using CodeIgniter controller functions.
(example)
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Me extends CI_Controller {
public function __construct()
{
parent::__construct();
if (is_logged_in()){if (is_admin()) { redirect('login'); }}
else { redirect('login');}
}
public function change_password()
{
$id=$this->session->userdata['user_data']['id'];
$data = json_decode(file_get_contents("php://input"));
$my_data=array(
'pass'=>$data->pass,
'new_pass'=>$data->new_pass,
);
$result=$this->vanesh_model->change_pass($id,$my_data);
if($result==1)
{
$arr = array('msg' => "Password changed successfuly.", 'error' => '');
$jsn = json_encode($arr);
print_r($jsn);
}
else if($result==2)
{
$arr = array('msg' => "", 'error' => 'Old Password is Invalid');
$jsn = json_encode($arr);
print_r($jsn);
}
else if($result==3)
{
$arr = array('msg' => "", 'error' => 'Sorry, Password change failed');
$jsn = json_encode($arr);
print_r($jsn);
}
}
}
?>
I am afraid of using angular session services, so I want to maintain sessions with only CI. What I am doing in my application is add, update, delete only if he is logged in. And I am using information stored in session. Consider the situation, suppose, I am logged in and doing something, side by side: I destroy the session using browser tools. Now I am continuing with application (doing operations like: change password). I have/had maintained error messages, success messages, its ok. If session OFF, it gives error message. But instead of error messages, I want to redirect to LOGIN page(with page refresh).
Note: For CI Login controller, I didn't used angular js. I have used angularjs only after login.
If by opening new tab I destroy the session, and come back to application's tab: I am able to perform tasks(may be with errors,). If session is OFF I see this in Browser's console: http://localhost/ums/login
This is because of CI constructor(please look over the code).
You should separate angular and CI as much as possible, since both have view-controller it creates a mess. Instead you should have CI in a separate folder, call it api, for example, after that anything you will need from CI should be acessed from angular with ajax calls.
I made a small webapp a while ago and this seemed to be the best way to organize code.
Few updates have been made to angular since then so if there's a better way please let me know
Solved.
Used javascript function. Checking session by http request everytime. If response comes "1". Means redirect to login as:
/* function for checking logged-in and role */
function check_session()
{
$.get("servercontroller/check_session", function(data, status){
if(data=="1") /* error 1 => un-athorized user */
{
window.location.href="/login-page-url";
}
});
}

Resources