I'm implementing a like/dislike feature using laravel. I got everything up and running except for the asynchronous request. I only want logged in users to be able to like/dislike an item so the request has to be verified server side. I also want it to be an asynchronous request, so no page refresh.
What would be a good way to do this? I already have jquery loaded so using angular JUST for that would be a bit of an overkill. How would I protect the request? I have read things about tokens, but I'm not quite sure about that.
The best way to do this would be to add a csrf filter to your config.php file. Laravel should generate csrf values for you by default but if not you can use it the following way with blade.
{{ csrf_token() }}
Your ajax could look something like this below. Note the csrf token could also be in your header tag as well but I put it here because of the way my code worked.
$.ajax({
type: "POST",
beforeSend: function(request) {
return request.setRequestHeader('X-CSRF-Token',"{{ csrf_token() }}");
},
url: baseLocalUrl, //this would be your path to your route
data:
{
html: sendHTML //data separated by a comma
},
success: function(data){
alert("Success");
},
error: function(xhr, textStatus, thrownError) {
alert('Failed');
}
});
For me on my last project, I have a bunch of forms and some ajax with csrf so I had to change the config filter to this because I had trouble mending the two so I created the following filter below.
Route::filter('csrf', function()
{
$token = Request::ajax() ? Request::header('x-csrf-token') : (Input::get('csrf_token') ?: Input::get('_token'));
$test1 = Session::getToken() != Input::get('csrf_token') && Session::getToken() != Input::get('_token');
$test2 = Session::token() != $token;
if($test2 && $test1){
throw new Illuminate\Session\TokenMismatchException;
}
});
Reference with more details
Related
I've Signup form in my website. It was properly submitting before. Now I wanted to submit my form using ajax and wanted to return a variable from controller into JSON that I will use into blade file.
The form is submitting and values are showing into database but after redirection, it returns error.
Undefined variable: seller in report blade
I tried to decode my variable to make it work but still the same error.
How would I make it work?
Report-Blade
#foreach(json_decode($seller, true) as $row)
<a href="{{route('Report', $row->id) }}" >
{{ __('Show Report of ')}} {{$row->car_title}}
</a>
#endforeach
Controller
$seller = Sellers::take(1)->latest()->get();
return response(view('report',array('seller'=>$seller)),200, ['Content-Type' =>
'application/json']);
JavaScript
$("#submit-all").click(function(e){
e.preventDefault();
var _token = $('input[name="_token"]').val();
$.ajax({
type: "post",
url: "{{ route('form_for_private_sellers') }}",
data : $('#msform').serialize() + "&_token=" + _token,
dataType: 'JSON',
beforeSend: function(){
// Show loading image
$("#se-pre-con").show();
},
success: function(data) {
window.location = "http://127.0.0.1:8000/report/";
},
complete:function(data){
// Hide loading image
$("#se-pre-con").hide();
}
});
});
As understood from your comments,
window.location = "http://127.0.0.1:8000/report/";
will hit the route
Route::get('/report', function () {
return view('report');
})->name('private_seller_report');
Report blade expects a variable named $seller, and it is not being sent from the route. You would need to change the route to something similar to this:
Route::get('/report', function () {
$sellers = Seller::get(); //your logic
return view('report', ['seller' => $sellers]);
})->name('private_seller_report');
Alternatively you can point the route to a method in a controller if you want to avoid bulking up your routes.
you need two route for this
first for rendering blade
return view('report');
and the second for fetch seller
$seller = Sellers::latest()->take(1)->get();
return $seller
I'm using Laravel 5.2. On my index page, I've got a button to add a new entry, which brings up the form in a lightbox. This form then submits via the create method.
I also have each entry listed on this page, with the ability to edit them inline, which submits via the update method.
I've setup validation via a Request. This means when someone misses something on the add, it redirects to the index method with errors. The errors only show though, when the lightbox is triggered by the user.
I know I can use $errors to see any errors, but I don't see how I can differentiate between the create and update forms for the sake of forcing the lightbox to appear on reload with create errors. Is there a way to do that?
Update:
Suggestion was made to use AJAX to bypass the reload issue, but now I'm getting a 422 return:
AJAX call:
(function(){
var submitAjaxRequest = function(e){
var form = $(this);
var method = form.find('input[name="_method"]').val() || 'POST';
$.ajax({
type: method,
url: form.prop('action'),
data: form.serialize(),
success: function(data){
console.log(data)
}
});
e.preventDefault();
}
$('form[data-remote]').on('submit', submitAjaxRequest);
})();
Request:
public function response(array $errors)
{
$response = parent::response($errors);
if ($this->ajax() || $this->wantsJson()) {
return $response;
}
return $response->with('requestMethod', $this->method());
}
I've also tested the ajax call and it works fine when the validation rules are met. It only fails if the validation comes back with something incorrect in the input.
You could override the response method so that you can flash the type of request.
In you Request class you could add
public function response(array $errors)
{
$response = parent::response($errors);
if ($this->ajax() || $this->wantsJson()) {
return $response;
}
return $response->with('requestMethod', $this->method());
}
(With ajax you wouldn't need to worry about the page reload so we can just return the original response.)
In the above I'm assuming you're using POST for your create methods and PUT or PATH for your update methods. If this is not the case you could use a way that make sense to you to differentiate between the requests.
Then in your view you could do something like:
#if(session('requestMethod') == 'POST')
https://laravel.com/docs/5.2/responses#redirecting-with-flashed-session-data
If you are going to use ajax, as I mentioned in the comment above, you will need to make sure you use the error method within the ajax call:
$.ajax({
type: method,
url: form.prop('action'),
data: form.serialize(),
success: function (data) {
console.log('success', data)
},
error: function (data) {
console.log('error', data)
}
});
Hope this helps!
I'm trying to parse the data that I send from my ajax form. But for some reason I cannot extract the information from the form in the php controller. Here's what I'm doing.
Here is the html:
<div style="margin-top: 100px;">
<h2>Character settings</h2>
<table class="table table-striped table-bordered table-hover">
<tr>
<th>Name</th>
<th>Map</th>
<th>Move</th>
</tr>
#foreach($chars as $char)
<tr>
<td>{{$char['name']}}</td>
<td>{{$char['map']}}</td>
<td>
{{Form::open(array('action' => 'UsersController#move', 'class' => 'mover'))}}
<input type="hidden" name="charID" class="charID" value="{{$char['id']}}" />
<button type="submit" class="btn btn-small btn-info">Move</button>
{{Form::close()}}
</td>
</tr>
#endforeach
</table>
Here is the javascript ajax processing:
$('#mover').on('submit', function(e){
e.preventDefault();
var $form = $( this ),
method = $form.attr( "method" );
$.ajax({
url: "{{action('UsersController#move')}}",
dataType: "json",
data: $form.find('.charID').val(),
type: method,
success: function (response) {
console.log(reponse['test']);
}
});
});
Here is the controller:
public function move() {
if(Auth::check()) {
Log::info(Input::get('charID')); //When I check the logs this is blank
$person = Character::where('id', '=', Input::get('charID'))->first();
$person->map = 100000000;
$person->save();
$response = array('status' => 'success', 'text' => 'Your character has been moved!');
return Response::json($response);
exit();
}
else {
return Redirect::action('PageController#showHome');
}
}
When I check the console log on submit I see the data "charID", so its being extracted by the form correctly, but I can't seem to get it in the laravel controller. Strange thing is I use the Input::get() function in other parts of my controller. So it's just this function.
Any help appreciated!
You're accessing response[test] instead of response[text].
Other things of note:
Your use of exit(); is redundant and will never be hit because you are returning above it.
Also, exit is a language construct and it can be called without parens if no status is passed. So if you are to use it without an argument, just use exit;.
Your ajax method could be optimised a bit more and it seems you also have another problem with your data, which needs to be sent as a key value pair.
You could do it as an inline string in the form of a GET request, or as an object like { "charID": $(this).find("input[name=charID]").val() },.
$("#mover").on("submit", function (e) {
e.preventDefault();
$.ajax({
"url": this.action, // <-- we can use native javascript
"dataType": "json",
"data": $(this).serialize(), // <-- collects your form data
"type": this.method,
"success": function (data) {
if (data.success !== "undefined" && data.success === 200) {
console.log(data.text);
}
}
});
});
I would recommend against using the Form helper class. It isn't very readable after-the-fact. You don't save that much time using it compared to writing out the HTML form definition anyway.
Let's optimise your controller so it's easier to understand as well:
public function move ()
{
if (Auth::check()) {
Log::info(($charID = request('charID')));
$person = Character::where('id', '=', $charID)->first();
$person->map = 100000000;
$person->save();
return response()->json([
'success' => 200, // success
'text' => 'Your character has been moved!'
]);
} else {
return response()->json([
'success' => 401 // unauthorised
]);
}
}
For a variable you're accessing more than once, I use a neat trick with parens (). If you wrap an assignment in parens e.g. ($charID = request('charID')) you can still use it's output in-line, with the added benefit that you now have access to it further into your script.
I'm opting for the helper methods like request() and redirect() instead of their class counterparts and the use of array() is a bit old hat - but that's just my opinion - I'd use square brackets [].
Your response when the user isn't authenticated is useless here assuming this controller action is only meant to handle posts from AJAX requests (if I'm not mistaken). Since it can't force your asynchronous request to redirect you. Instead we return the proper HTTP response code 401 to represent that the user was unauthorised.
You could also look up PSR-2 standards for your code structure, as that is what is used as an industry standard nowadays.
Lastly, if you are using the web middleware (with CSRF protection) on the controller here. You need to send a CSRF token with the request. You can do that by popping {{ csrf_field() }} into the form. The form helper may be doing this for you.
But another problem you would have stumbled into if you were not using $(this).serialize() in the AJAX setup is that the _token field would never have been sent along with the request.
Try
data: { 'charID': $form.find('.charID').val() },
as it is now you're only sending the value, there's no way PHP let alone Laravel will know its name
I see a few potential issues with this code.
You're initializing your form with 'class' => 'mover', but your jquery is looking for $('#mover') - So your form tag has the class 'mover' but your jquery expects an id 'mover'
You're setting the ajax method to $form.attr( "method" ); but I'm not sure if method is getting set at all in your form tag? Why not just set your ajax type/method to POST since that's the appropriate method for posting a form?
try to change as below,
$.ajax({
url: "{{action('UsersController#move')}}",
dataType: "json",
data: $form.serialize(),
type: 'post',
success: function (response) {
console.log(reponse['test']);
}
});
I have a problem since 1 week more or less and when I leave open the page of my application but without using it for maybe 4 or 5 hours and return to make a request (a click on a button to load something or things like that) everytime in the console display this:
{
"error":{
"type":"Illuminate\\Session\\TokenMismatchException",
"message":"",
"file":"C:\\xampp182\\htdocs\\mysite\\app\\filters.php",
"line":97
}
}
I'm using CSRF protección in my POST request passing for the ajax request this configuration
$.ajax({
url: '/path/to/file',
type: 'default GET (Other values: POST)',
dataType: 'default: Intelligent Guess (Other values: xml, json, script, or html)',
data: {param1: 'value1'},
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
})
.done(function() {
console.log("success");
})
.fail(function() {
console.log("error");
})
.always(function() {
console.log("complete");
});
This is my filters.php
/*
|--------------------------------------------------------------------------
| CSRF Protection Filter
|--------------------------------------------------------------------------
|
| The CSRF filter is responsible for protecting your application against
| cross-site request forgery attacks. If this special token in a user
| session does not match the one given in this request, we'll bail.
|
*/
Route::filter('csrf', function($route, $request)
{
if (strtoupper($request->getMethod()) === 'GET')
{
return; // get requests are not CSRF protected
}
$token = Request::ajax() ? Request::header('x-csrf-token') : Input::get('_token');
if (Session::token() != $token)
{
throw new Illuminate\Session\TokenMismatchException; //Line 97
}
});
Edit
I see the problem, the Session::token() and $token are different but is there a way to regenerate or put the both tokens with the same value?
This is how CSRF protection works. After session lifetime it generates new token. No one normally will fill in the form for 4-5 hours. If you do it just for testing on your localhost you may change lifetime in app/config/session to higher value and it should work.
Otherwise you may probably change:
throw new Illuminate\Session\TokenMismatchException; //Line 97
into something like that
return Redirect:back()->withInput(Input::except('token'))->with('_token',Session::token());
I had this problem on login also. From time to time this exception occurred so I stop and tried to reproduce it. I succeed by doing this:
First I load the login page.
Then I deleted the cookies.
Then, without reloading the login page, I entered username and password and tried to login.
Because session was deleted (when I deleted the cookies), it was normal that this code was not going to pass and it will throw the TokenMismatchException.
Route::filter('csrf', function() {
if ( Session::getToken() != Input::get('_token')) {
throw new Illuminate\Session\TokenMismatchException;
}
});
So, what I've done to solve my problem was to add a redirect to login page with a message to inform the user that the session might expired.
Route::filter('csrf', function() {
if ( Session::getToken() != Input::get('_token')) {
return Redirect::to('/admin/login')->with('warning', 'Your session has expired. Please try logging in again.');
}
});
Thus, after page reloading, a new session is created and the problem is solved.
Simply place the code below into your filters:
if (Session::token() !== $token)
{
return Redirect::back()->withInput(Input::except('_token'));
}
At this moment I am using laravel. In this context I am having a form which is successfully submitted by using ajax to a controller. and that controller make it to the database. But the problem is as the ajax is doing its job the whole page remain unmoved / unchanged after the submission even the database is updated.
Now what I want
I want to give feedback to the user that your post is successfully submitted there. or what I want to do in further, I want to refresh the section in which the post is collected from the database as this post can be retrieved from there. But by using ajax only.
So there is no need to collect the whole page or refresh.
here is my form structure
`
{{ Form::open(array('route' => array('questions.store'), 'class' => 'form-horizontal' )) }}
blah blah blaaa .......
<script type="text/javascript">
$(".form-horizontal").submit(function(e){
$(this).unbind("submit")
$("#ask").attr("disabled", "disabled")
var that = $(this),
url = that.attr('action'),
type = that.attr('method'),
data = {};
that.find('[name]').each(function(index, value){
var that = $(this),
name = that.attr('name'),
value = that.val();
data[name] = value;
});
$.ajax({
url: url,
type: type,
data: data,
success: function(response){
console.log(response);
}
});
return false;
});
</script>
{{ Form::close() }}
`
As it is very much visible that the post is updated through a route & controller I want to have another dive and a success message at this script to be displayed after the success of posting. I am looking for some professional structure using what there is minimal need to have interaction with the server side and give user a better page viewing experience.
Thanks a lot for helping me in this research.
I am not sure if I understand you well, but if you want to notify the user about the result of an ajax-called db update you need to have
a route for the ajax save db call - it should point to a method that does the db update.
the db update method should return some value indicating the success/failure of update (for example OK or FAIL)
the only result of calling the method will be just plain text page with OK or FAIL as body
fetch the result by ajax and inform user accordingly (after form submit button)
check out the below code for ajax call itself (inside the form submit handler) to see what I mean
var db_ajax_handler = "URL_TO_YOUR_SITE_AND_ROUTE";
var $id = 1; //some id of post to update
var $content = "blablabla" //the cotent to update
$.ajax({
cache: false,
timeout: 10000,
type: 'POST',
tryCount : 0,
retryLimit : 3,
url: db_ajax_handler,
data: { content: $content, id: $id }, /* best to give a CSRF security token here as well */
beforeSend:function(){
},
success:function(data, textStatus, xhr){
if(data == "OK")
{
$('div.result').html('The new Question has been created');
}
else
{
$('div.result').html('Sorry, the new Question has not been created');
}
},
error : function(xhr, textStatus, errorThrown ) {
if (textStatus == 'timeout') {
this.tryCount++;
if (this.tryCount <= this.retryLimit) {
//try again
$.ajax(this);
return;
}
return;
}
if (xhr.status == 500) {
alert("Error 500: "+xhr.status+": "+xhr.statusText);
} else {
alert("Error: "+xhr.status+": "+xhr.statusText);
}
},
complete : function(xhr, textStatus) {
}
});
EDIT: as per comment, in step 2 (the method that is called with AJAX) replace
if($s)
{
return Redirect::route('questions.index') ->with('flash', 'The new Question has been created');
}
with
return ($s) ? Response::make("OK") : Response::make("FAIL");
EDIT 2:
To pass validation errors to the ajax-returned-results, you cannot use
return Response::make("FAIL")
->withInput()
->withErrors($s->errors());
as in your GIST. Instead you have to modify the suggested solution to work on JSON response instead of a plain text OK/FAIL. That way you can include the errors in the response and still benefit from the AJAX call (not having to refresh the page to retrieve the $errors from session). Check this post on the Laravel Forum for a working solution - you will get the idea and be able to fix your code.