FOSUserBundle AJAX Login with Symfony2 (routing) - javascript

I'm trying to make the AJAX authentication work with FOSUserBundle.
I have created an Handler directory with a AuthenticationHandler class :
<?php
namespace BirdOffice\UserBundle\Handler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
private $router;
private $session;
/**
* Constructor
*
* #param RouterInterface $router
* #param Session $session
*/
public function __construct( RouterInterface $router, Session $session )
{
$this->router = $router;
$this->session = $session;
}
/**
* onAuthenticationSuccess
*
* #param Request $request
* #param TokenInterface $token
* #return Response
*/
public function onAuthenticationSuccess( Request $request, TokenInterface $token )
{
// if AJAX login
if ( $request->isXmlHttpRequest() ) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
// if form login
} else {
if ( $this->session->get('_security.main.target_path' ) ) {
$url = $this->session->get( '_security.main.target_path' );
} else {
$url = $this->router->generate( 'home_page' );
} // end if
return new RedirectResponse( $url );
}
}
/**
* onAuthenticationFailure
*
* #param Request $request
* #param AuthenticationException $exception
* #return Response
*/
public function onAuthenticationFailure( Request $request, AuthenticationException $exception )
{
// if AJAX login
if ( $request->isXmlHttpRequest() ) {
$array = array( 'success' => false, 'message' => $exception->getMessage() ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
// if form login
} else {
// set authentication exception to session
$request->getSession()->set(SecurityContextInterface::AUTHENTICATION_ERROR, $exception);
return new RedirectResponse( $this->router->generate( 'login_route' ) );
}
}
}
I have created a login Javascript function :
function login() {
$.ajax({
type: "POST",
url: Routing.generate('check_login_ajax'),
dataType: 'json',
data: {
_username: $('#username').val(),
_password: $('#password').val(),
_remember_me: false,
_csrf_token: $('#_csrf_token').val()
}
}).done(function(data) {
console.log(data);
}).fail(function(data) {
console.log(data);
});
}
In my routingAjax.yml, I have added the following lines to override the FOSUserBundle security route :
check_login_ajax:
pattern: /check_login_ajax
defaults: { _controller: FOSUserBundle:Security:check }
requirements:
_method: POST
options:
expose: true
In my global security.yml file, I have added the check_path, success_handler and failure_handler parts :
firewalls:
main:
pattern: ^/
form_login:
login_path: fos_user_registration_register
check_path: check_login_ajax
success_handler: user.security.authentication_handler
failure_handler: user.security.authentication_handler
provider: fos_userbundle
csrf_provider: form.csrf_provider
logout:
path: fos_user_security_logout
target: /
anonymous: true
My first issue is : the AJAX return this message: "Invalid CSRF token." (but I send a good one generated in PHP, maybe I missed something doing it). Here is my PHP code for it :
<?php
$csrfProvider = $this->container->get('form.csrf_provider');
$csrfToken = $csrfProvider->generateCsrfToken('popUpUser');
?>
Second issue : my login page (not the AJAX one) is not working anymore because the orignal route of FOSUserBundle login has been overwritten.
PS : I have posted a message yesterday : FOSUserBundle (login / register) + AJAX + Symfony2 but I have badly explained my problem. Sorry by advance.

First Issue: You are sending an invalid CSRF token. In Symfony 2.3 you could generate it using {{ csrf_token('authenticate') }} inside the template's input's value.
Second issue: Do not overwrite the route, simply use the original route: fos_user_security_check.
In general: if you use an AuthenticationSuccessHandler extending Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler your method could look something like this:
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
return new JsonResponse(array('success' => true));
}
return parent::onAuthenticationSuccess($request, $token);
}
Do something similar for an AuthenticationFailureHandler extending Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler.

Related

Pusher: Callback function not executing with standalone Laravel (API) Vue.js(client) apps

Please I need help with pusher integration in my Laravel & Vue js project. NOT SPA (i.e separate Apps (Laravel - API & Vuejs- frontend)
The goal is to establish a real-time chat between two users.
The whole cycle is working perfectly well but the pusher callback is not executing, therefore making the chat function limited to the app API level only. It is not real-time which is why I'm integrating pusher to handle that.
Please see the code snippets below, ready to provide more on request. I've spent days on this, still can't figure out what I'm doing wrong.
Thanks in anticipation.
CommentController
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$user = Auth::user();
$validator = Validator::make($request->all(), [
'bid_id' => 'required',
'message' => ['required', 'string'],
]);
if ($validator->fails()) {
return response()->json(["error" => $validator->errors()], 400);
}
try {
$comment = $user->comments()->create([
'bid_id' => $request->bid_id,
'message' => $request->message,
]);
// Fire the comment broadcast event
// event(new CommentEvent($comment));
broadcast(new CommentEvent($user, $comment->load('user')))->toOthers();
} catch (Exception $exception) {
Log::error("Error while creating Comment" . $exception->getMessage());
} finally {
return response()->json(['comment' => $comment], 201);
}
}
CommentEvent.php
<?php
namespace App\Events;
use App\Models\Comment;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CommentEvent implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* #param $comment
*
* #return void
*/
public $user;
public $comment;
public function __construct(User $user, Comment $comment)
{
$this->user = $user;
$this->comment = $comment;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PresenceChannel('comment-channel');
}
public function broadcastAs()
{
return 'CommentEvent';
}
}
Main.js
import Pusher from "pusher-js";
/* -------------------------------------------------------------------------- */
/* PUSHER CONFIG */
/* -------------------------------------------------------------------------- */
Pusher.logToConsole = true;
let pusher = new Pusher(process.env.VUE_APP_PUSHER_APP_KEY,{
cluster: process.env.VUE_APP_PUSHER_APP_CLUSTER,
encrypted: false,
});
Vue.prototype.$pusher = pusher;
App.vue
<script>
export default {
name: "App",
components: {},
created() {
let channel = this.$pusher.subscribe("comment-channel");
channel.bind("pusher:subscription_succeeded", function(members) {
console.log(members);
console.log("succesfully subscribed!");
});
channel.bind("CommentEvent", function(data) {
console.log(data);
this.$store.commit("ADD_COMMENT", data.comment);
});
},
methods: {
},
};
</script>
I've been able to resolve this using.
but I had to switch tech. The real-time chat system of my app is now driven by socket.io, Redis and a simple node js server wrapped within the API
I'll be willing to help with code snippets if you need me to.

PayPal Smart Payment Buttons: Error: JSON.parse: unexpected character at line 1 column 1 of the JSON data

I've been trying to figure out this problem for 2 days already..
I want to implement Smart Payment Buttons from PayPal, literally followed every step of the explanation closely but still getting following error:
Error: JSON.parse: unexpected character at line 1 column 1 of the JSON data
My javascript for Button rendering:
paypal.Buttons({
createOrder: function() {
return fetch('vendor/paypal/paypal-checkout-sdk/samples/CaptureIntentExamples/CreateOrder.php', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID; // Use the same key name for order ID on the client and server
});
},
onApprove: function(data, actions) {
// This function captures the funds from the transaction.
return actions.order.capture().then(function(details) {
// This function shows a transaction success message to your buyer.
alert('Transaction completed by ' + details.payer.name.given_name);
});
},
onError: function(err) {
alert(err);
}
}).render('#paypal-button-container');
My CreateOrder.php:
namespace Sample\CaptureIntentExamples;
require __DIR__ . '/../../../../autoload.php';
use Sample\PayPalClient;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
class CreateOrder
{
/**
* Setting up the JSON request body for creating the Order. The Intent in the
* request body should be set as "CAPTURE" for capture intent flow.
*
*/
private static function buildRequestBody()
{
return array(
'intent' => 'CAPTURE',
'application_context' =>
array(
'return_url' => 'https://example.com/return',
'cancel_url' => 'https://example.com/cancel'
),
'purchase_units' =>
array(
0 =>
array(
'amount' =>
array(
'currency_code' => 'USD',
'value' => '220.00'
)
)
)
);
}
/**
* This is the sample function which can be sued to create an order. It uses the
* JSON body returned by buildRequestBody() to create an new Order.
*/
public static function createOrder($debug=false)
{
$request = new OrdersCreateRequest();
$request->headers["prefer"] = "return=representation";
$request->body = self::buildRequestBody();
$client = PayPalClient::client();
$response = $client->execute($request);
if ($debug)
{
print "Status Code: {$response->statusCode}\n";
print "Status: {$response->result->status}\n";
print "Order ID: {$response->result->id}\n";
print "Intent: {$response->result->intent}\n";
print "Links:\n";
foreach($response->result->links as $link)
{
print "\t{$link->rel}: {$link->href}\tCall Type: {$link->method}\n";
}
// To toggle printing the whole response body comment/uncomment below line
echo json_encode($response->result, JSON_PRETTY_PRINT), "\n";
}
return $response;
}
}
if (!count(debug_backtrace()))
{
CreateOrder::createOrder(true);
}
It's basicly all copied from the PayPal walkthough.
If I visit the CreateOrder.php directly it is creating an order and I can see the response without errors.
Status Code: 201 Status: CREATED [...]
What I did was deleting the part of the code which was printing out the response in txt format. This is why you were getting JSON syntax error.
public static function createOrder($debug=false)
{
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = self::buildRequestBody();
// 3. Call PayPal to set up a transaction
$client = PayPalClient::client();
$response = $client->execute($request);
// To print the whole response body, uncomment the following line
echo json_encode($response->result, JSON_PRETTY_PRINT);
// 4. Return a successful response to the client.
return $response;
}
By the way, this answer is very helpful: https://stackoverflow.com/a/63019280/12208549

Laravel: FormRequest validation failing when I add required rule

I have a FormRequest class set up to validate input from my frontend (in the shape of the FormData object) however it's acting really weird with my fields (title and body).
Despite FormData being sent (I check network tab and do $request->all()) I'm getting the title and body fields are required 244 validation error,
I also noticed that after removing the required rule the validation PASSES SUCCESSFULLY even if my both my inputs are less than 5 characters (this shouldn't happen). Any idea what could be causing this?
So right now if required rule my input passes and is added to the database if I add it back validation fails and field required message pops up.
My FormRequest:
<?php
namespace App\Http\Requests\bulletins;
use Illuminate\Foundation\Http\FormRequest;
class CreateBulletin extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'image' => 'nullable|sometimes|image|mimes:jpeg,bmp,png,jpg,svg|max:2000',
'title' => 'string|min:5|max:250',
'body' => 'string|min:5|max:250',
];
}
}
My controller method:
public function store(CreateBulletin $request)
{
//dd($request->all());
$bulletin = new Bulletin();
$bulletin->title = $request->input('title');
$bulletin->body = $request->input('body');
$bulletin->image = '/img/others/default.jpg';
if($request->hasFile('image')){
$uploadFile = $request->file('image');
$filename = str_random(6).'.'.$uploadFile->extension();
$uploadFile->storeAs('uploads', $filename);
$bulletin->image = '/uploads/'.$filename;
}
$bulletin->save();
return response()->json([
'bulletin' => $bulletin,
]);
}
The shape of my data being sent:
Checking parameters sent at network tab:
-----------------------------1607382142848
Content-Disposition: form-data; name="title"
title of bulletin
-----------------------------1607382142848
Content-Disposition: form-data; name="body"
content of bulletin
-----------------------------1607382142848--
OR
after doing dd($request->all())
array:3 [
"title" => "title of bulletin"
"body" => "content of bulletin"
"image" => UploadedFile {#971
-test: false
-originalName: "01-uxpin-modal-signup-form.jpg"
-mimeType: "image/jpeg"
-error: 0
#hashName: null
path: "C:\xampp\tmp"
filename: "php7708.tmp"
basename: "php7708.tmp"
pathname: "C:\xampp\tmp\php7708.tmp"
extension: "tmp"
realPath: "C:\xampp\tmp\php7708.tmp"
aTime: 2018-12-04 11:45:56
mTime: 2018-12-04 11:45:56
cTime: 2018-12-04 11:45:56
inode: 0
size: 48989
perms: 0100666
owner: 0
group: 0
type: "file"
writable: true
readable: true
executable: false
file: true
dir: false
link: false
linkTarget: "C:\xampp\tmp\php7708.tmp"
}
]
So as you can see my data hits the server
Try something like this and see if you get same error:
<?php
namespace App\Http\Requests\bulletins;
use App\Http\Requests\Request;
class CreateBulletin extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'image' => 'nullable|sometimes|image|mimes:jpeg,bmp,png,jpg,svg|max:2000',
'title' => 'string|min:5|max:250',
'body' => 'string|min:5|max:250',
];
}
}
Need to remove FormRequest from class to remove that error you got after replacing Request.
Hope this helps.
Before set the values you have to test the request using:
$validated = $request->validated();
then(for example the title field which has a rule in the form request):
$bulletin->title = $validated->input('title');
Make sure of the imports in the controller also:
use Illuminate\Http\Request; for the request;
and
use App\Http\Requests\CreateBulletin;

Symfony3 send AJAX POST request

I want to send two variables id and commentary through an AJAX POST request.
The problem is that I don't get the POST variable but the route is reached.
JS:
$.post(Routing.generate('ajax_savecommentary', { id:id, commentary:commentary }),
function(response)
{
}, "json");
Symfony:
public function saveCommentaryAction()
{
if (!$this->get('session')->get('compte'))
return $this->redirect($this->generateUrl('accueil'));
$request = $this->container->get('request_stack')->getCurrentRequest();
$isAjax = $request->isXMLHttpRequest();
if ($isAjax)
{
$information = $this->getDoctrine()->getManager()->getRepository('CommonBundle:Information')->find($_POST['id']);
$information->setCommentaire(str_replace('\n', '\\n', $_POST['commentary']));
$this->getDoctrine()->getManager()->flush();
$response = array("code" => 100, "success" => true, 'commentary' => $_POST['commentary']);
return new Response(json_encode($response));
}
$response = array("code" => 0, "success" => false);
return new Response(json_encode($response));
}
The error:
http://localhost/MyProject/web/app_dev.php/ajax/save/commentary/?id=61&commentary=MyCommentary.
{"code":0,"success":false}
More Symfony error:
GET Parameters
Key/Value
commentary/MyCommentary
id/61
And the routing is case needed:
ajax_savecommentary:
defaults: { _controller: CommonBundle:Default:saveCommentary }
path: /ajax/save/commentary/
options:
expose: true
Try using the request passed to the Controller Action instead of retrieve it from the container. So try this:
use Symfony\Component\HttpFoundation\Request;
...
public function saveCommentaryAction(Request $request)
{
if (!$this->get('session')->get('compte'))
return $this->redirect($this->generateUrl('accueil'));
$isAjax = $request->isXMLHttpRequest();
instead of this:
public function saveCommentaryAction()
{
if (!$this->get('session')->get('compte'))
return $this->redirect($this->generateUrl('accueil'));
$request = $this->container->get('request_stack')->getCurrentRequest();
$isAjax = $request->isXMLHttpRequest();
UPDATE:
You can restrict your routing with Customized Route Matching with Conditions, as example on your case as follow:
ajax_savecommentary:
defaults: { _controller: CommonBundle:Default:saveCommentary }
path: /ajax/save/commentary/
options:
expose: true
condition: "request.isXmlHttpRequest()"
methods: [POST]
UPDATE:
There is a typo in the routing generation in the JS side:
$.post(Routing.generate('ajax_savecommentary', { id:id, commentary:commentary }),
function(response)
{
}, "json");
you pass the data as argument of the routing.generate function so it concatenate the params as query string. so try this:
$.post(Routing.generate('ajax_savecommentary'), { id:id, commentary:commentary },
function(response)
{
}, "json");
Another advice is about to use the $request object for obtain the data instead of the superglobal PHP attribute, so use:
$request->request-get('commentary');
instead of:
$_POST['commentary']
More info here in the doc.
Hope this help

Angular, Laravel 4 and MySQL database best practices

I am pretty new to Angular and have a bit of experience with Laravel 4.
I am building an app where the user can edit "on-the-fly" but also save to a MySQL.
The initial plan was to use Angular to manage the live editing, and to store and retrieve data to the MySQL DB using Eloquent. I am aware you can connect to DB via Angular, but I'd like to know what the best solution would be.
Is it best to keep it separated or to use Angular for everything?
Is there some performance issues if using Angular for everything?
Is Angular as easy to use as Eloquent for DB interactions ?
If I use Eloquent for DB, is it straight-forward to pass data to Angular?
I have already started building the live-editing part of the app with Angular and I have to say I found it very easy to learn and extremely powerful. I now have to make a decision as to how I will handle storage.
Thank you
Check out this tutorial by the great Dave Mosher, I think it might be exactly what you're looking for, he uses Laravel, Angular, and MySQL:
Youtube Screencast: http://www.youtube.com/watch?v=hqAyiqUs93c
Source code: https://github.com/davemo/end-to-end-with-angularjs
The best way to use angular.js and laravel is, using a REST API.
For example, if you have an admin panel to manage users, the method will be,
In your route,
Route::resource('users', 'UsersController');
The controller looks like this,
<?php
/**
*
* Users Controller
*
*/
class UsersController extends AdminController {
/**
* Display all users.
*
* #return Response
*/
public function index() {
$users = User::where('id', '!=', Auth::user()->id)->get();
return Response::json(array(
'status' => 'success',
'users' => $users->toArray()),
200
);
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create() {
//
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store() {
// add some validation also
$input = Input::all();
$user = new User;
if ( $input['name'] ) {
$user->name = $input['name'];
}
if ( $input['username'] ) {
$user->username = $input['username'];
$user->password = Hash::make($input['username']);
}
if ( $input['email'] ) {
$user->email = $input['email'];
}
$user->save();
return Response::json(array(
'status' => 'success',
'users' => $user->toArray()),
200
);
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id) {
$user = User::where('id', $id)
->take(1)
->get();
return Response::json(array(
'error' => false,
'users' => $user->toArray()),
200
);
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return Response
*/
public function edit($id) {
//
}
/**
* Update the specified resource in storage.
*
* #param int $id
* #return Response
*/
public function update($id) {
// add some validation also
$input = Input::all();
$user = User::find($id);
if ( $input['name'] ) {
$user->name = $input['name'];
}
if ( $input['username'] ) {
$user->username = $input['username'];
}
if ( $input['email'] ) {
$user->email = $input['email'];
}
$user->save();
return Response::json(array(
'status' => 'success',
'message' => 'User Updated'),
200
);
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
*/
public function destroy($id) {
$user = User::find($id);
$user->delete();
return Response::json(array(
'status' => 'success',
'message' => 'User Deleted'),
200
);
}
}
Then the script,
var app = angular.module('myApp', []);
// include this in php page to define root path
app.factory('Data', function(){
return {
root_path: "<?php echo Request::root(); ?>/"
};
});
GET - Get all users
$http({method: 'GET', url: Data.root_path + 'users'}).
success(function(data, status, headers, config) {
$scope.users = data.users;
}).
error(function(data, status, headers, config) {
$scope.users = [];
});
GET - Get single user for edit
$http({method: 'GET', url: Data.root_path + 'users/'+id}).
success(function(data, status, headers, config) {
$scope.entry = data.users[0];
}).
error(function(data, status, headers, config) {
$scope.entry = [];
});
PUT - Update single user
$http.put(Data.root_path + 'users/'+entry.id, entry).
success(function(data, status, headers, config) {
//
}).
error(function(data, status, headers, config) {
//
});
POST - Save new user
$http.post(Data.root_path + 'users', entry).
success(function(data, status, headers, config) {
//
}).
error(function(data, status, headers, config) {
//
});
DELETE - Delete a user
$http.delete(Data.root_path +'users/'+id)
.success(function(response) {
//
})
.error(function(response) {
//
});
Create RESTful endpoints to manage resources using Laravel. This is dead simple with the resource controller as already pointed out:
Route::resource('user', 'UsersController');
Then you use an AngularJS resource to interact with that. Example (from docs) of fetching a user, editing the model and then saving (calling via the $ in the callback).
var User = $resource('/user/:userId', {userId:'#id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
http://docs.angularjs.org/api/ngResource.$resource
Even better, create an Angular JS Service for the resource so you can just inject that into multiple controllers and get all the methods defined.
Here's a great post on how to set this up:
http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource

Categories

Resources