Getting TokenMismatchException when using nested AJAX calls. Laravel 5.4 - php

I get TokenMismatchException when using nested AJAX calls. The first AJAX call works fine but the second always goes to error instead of success.
What I'm trying to do is that when the user registers from the button in the nav bar I want him to go to the dashboard or /home - this works okay. But, when the user fills the form (to buy something) on the index page, I want him to:
Have his input checked for validity, then, check if he's logged in, if not then the registration modal pops up. After he's registered I want him to be redirected to the checkout page.
However, what happens is that when the user fills the buying form and hits submit, the first ajax checks if the input in the buying form is valid, if it is, then check if he's logged in if not return 401 error.
401 gets picked up by the first ajax and directs the flow to 401 handling where the registration modal pops up to register, that's when the 2nd ajax pop up. After he's registered the back-end keeps returning 500 because of CSRF token mismatch.
First, this is the nested ajax:
<script type="text/javascript">
$(document).ready(function(){
$('#topup-form').submit(function(e){
e.preventDefault();
var topup_info = $('form').serialize();
//FIRST AJAX
$.ajax({
url: $('form').attr('action'),
method: 'post',
data: topup_info,
type: 'json',
//if success show success message for user
success: function(result){
alert(result.responseJSON.code);
$('.alert.error').slideUp(200);
$('.alert.success').append("<p class='lead'>Thanks! To checkout we go!</p>").slideDown(200);
},
//for error check if it's 400 (validation) or 401(authentication)
error: function(errorData){
// alert(errorData.responseJSON.code);
if(errorData.responseJSON.code === 400){
var error = errorData.responseJSON.message;
$('.alert.error').text('');
$('.alert.success').slideUp(200);
for (var i in error){
for (var j in error[i]) {
var message = error[i][j];
$('.alert.error').append("<p class='lead'>" + message + "<p>");
}
}
$('.alert.error').slideDown(00);
}//end error 400
//for authentication failure, show registeration modal
else if (errorData.responseJSON.code === 401) {
//change somethings in registeration modal
$('#myModalLabel').html('Please Login First');
$('#register').trigger('click');
document.getElementById('formRegister').action = "{{ route('user.regtopup') }}";
//when registeration form is submitted..
$('#formRegister').submit(function(e){
e.preventDefault();
//fire 2nd ajax
$.ajax({
url: $('#formRegister').attr('action'),
method: 'post',
data: $('form').serialize(),
type: 'json',
success: function(result){
alert('success!!!');
},
//it keeps going to error! complaining about csrf token mismatch
error: function(result){
console.log(JSON.stringify(result));
},
})//end of 2nd ajax
});//end of 2nd submit
}//end of 401
}//end of error
});//end of first ajax
});//end of first submit
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
})
});
</script>
Second, this is the controller that checks input validity and return 401 when not registered:
public function etiPost(Request $request) {
$validator = [
'topupAmount'=> 'required|integer|between:10,500',
'phonenumber'=> 'required|regex:/^05[602][0-9]{7}$/',
];
$inputs = $request->all();
Log::info($inputs);
$validator = Validator::make($inputs, $validator);
if($validator->fails()){
return Response::json([
'error' => true,
'message' => $validator->messages(),
'code' => 400
], 400);
}
elseif (Auth::check()) {
return view('pages.checkout', compact('inputs'));
}
else {
return Response::json([
'error' => true,
'message' => "Please login first",
'code' => 401
], 401);
}
}
This is the overloaded register method that returns JSON when registration is successful. Here is where 500 is returned! When I Log the returned JSON it comes out as normal 200 response but it arrives at the "Whoops" 500 error to the 2nd ajax! The user is registered successfully in the database but this method returns 500 which is caught by the error part of the ajax call.
/**
* Handle a registration request for the application (overloaded).
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
$this->guard()->login($this->create($request->all()));
// return response()->json();
return response()->json(['msg' => 'Success! You have been registered!'], 200);
}
I won't include the forms for brevity but rest assured I added all the CSRF input tags and the meta tag in the head of the HTML.
What should I do differently to avoid this? The first ajax works but the second doesn't.

Set header token for each ajax call
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') },
Also note that you have to add mete token in your template
you can add meta token like this in template
<meta name="csrf-token" content="{{ csrf_token() }}">
if you still want to disable csrf token then
Excluding URIs From CSRF Protection
Sometimes you may wish to exclude a set of URIs from CSRF protection. For example, if you are using Stripe to process payments and are utilizing their webhook system, you will need to exclude your Stripe webhook handler route from CSRF protection since Stripe will not know what CSRF token to send to your routes.
Typically, you should place these kinds of routes outside of the web middleware group that the RouteServiceProvider applies to all routes in the routes/web.php file. However, you may also exclude the routes by adding their URIs to the $except property of the VerifyCsrfToken middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'stripe/*',
];
}
Instead of 'stripe/*', if you give '/*' then it will disable token for all
For more detail :
https://laravel.com/docs/5.5/csrf#csrf-x-csrf-token

please follow code in your jquery before ajax call
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
it's not advisable to add CSRF token in meta because all pages are not contain form to submit value so use this solution so you can use CSRF only for your js perspective.
Thank you

Related

Laravel 5 App - AJAX Post Request not accepting the token and throwing a 500 internal server error

So, I've been looking on here for the better part of 3 hours as to why my code isn't working. I don't think my ajax request is detecting my CSRF token even though i've tried multiple ways to implement it. New to AJAX requests so go easy.
Goal:
Submit an ajax POST request to /email/subscribe/ and add the email address provided by the user to the email_subscribers table.
I've tried adding the following to my code to get the token to show up:
The meta tag and the Ajax Setup.
Top of the file
<meta name="csrf-token" content="{{ csrf_token() }}">
In the script tag INSIDE the jquery $(document).ready() function
// CSRF Ajax Token
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
Hidden input field
I've also added a hidden input field and tried to insert the token inside the data object inside the ajax post.
HTML
<input type="hidden" id="token" value="{{ csrf_token() }}">
Javascript tag
var token = $('#token').val();
// var token = $('meta[name="csrf-token"]').attr('content'); //Tried this way
$.ajax({
type: 'POST',
url: '/email/subscribe/',
dataType: 'json',
data: {
email: email,
'_token': token,
// "_token": "{{ csrf_token() }}", //Tried this way as well
"_method": 'POST',
},
success: function (data) {
// Check Server Side validation
if($.isEmptyObject(data.errors)){
console.log(data['success']);
}else{
// Validation Failed Display Error Message
console.log(data.errors);
}
},
error: function (data) {
console.log(data);
}
}); // End Ajax POST function
Here is my web.php file
// Email Routes
Route::prefix('email')->group(function() {
// Create Post Route for subscribing
Route::post('/subscribe', 'EmailSubscriptionsController#subscribe')->name('email.subscribe');
});
and my EmailSubscriptionsController
class EmailSubscriptionsController extends Controller
{
// Store the Email into the database
public function subscribe(Request $request) {
// Validate the request
$validator = Validator::make($request->all(), [
'email' => 'required|email',
]);
// If the validation fails
if($validator->fails()) {
return response()->json([
'errors' => $validator->errors()->all(),
]);
}
// New Section Object
$subscription = new EmailSubscription;
// Add Name into Section Object
$subscription->email = $request->email;
// Save the Section
$subscription->save();
// Return The Request
return response()->json([
'success' => 'Record has been saved successfully!'
]);
} // End Subscribe
} // End Controller
Whats really weird is the fact that when i submit the request with NOTHING but the following in my subscribe() function inside my controller:
// Return The Request
return response()->json([
'success' => 'Record has been saved successfully!'
]);
It doesn't return an error.... Just passes the success message to the console. I'm not sure what i'm doing wrong as I have this working (an ajax post request) in another portion of my site.
Start by looking in storage/logs/laravel.log for the exception stack trace. That should give a more clear indication of what is failing.
The web inspector also allows you to see the response which usually includes the trace.
A common cause of 500 ISEs is improperly importing classes via use.
Use It Like This:-
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}

Laravel 5.2 return with errors - how to tell which form is submitted

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!

How to catch validation failure with laravel when using form requests

I'm using a formrequest file with laravel 5.2 to check input. This form i'm calling using jquery's $.post function. In my console, it's return 422 Unprocessable Entity which I suspect to be coming from the response since i'm not formatting it to json. One way of doing is this
I'd would like to know how I can invoke from my form request, the means to change the output messages to json.
Thanks!
UPDATE 1
JQuery looks like this:
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('input[name="csrf-token"]').val()
}
});
$("#changePassword").on('click', function(e){
e.preventDefault();
var data = {};
data.name = $("input[name='name']").val();
data.surname = $("input[name='surname']").val();
data._token = $('input[name="_token"]').val();
$.post('url',data).done(function(data){
$(".message").empty().html("Done!");
}).fail(
function(response, status){
}
);
});
Override the response method in your request class like below
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
return Response::json([
'error' => [
'message' => $errors,
]
]);
}

Laravel 4, Token mismatch exception in filters.php using CSRF

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

Defined route throwing controller method not found laravel 4

I have a route defined in routes.php file but when i make an ajax request from my angular app, i get this error
{"error":{"type":"Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException","message":"Controller method not found.","file":"C:\\xampp\\htdocs\\tedxph\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Controllers\\Controller.php","line":290}}
this is my routes file
/*
|--------------------------------------------------------------------------
| Api Routes
|--------------------------------------------------------------------------
*/
Route::group(array('prefix' => 'api'), function() {
//Auth Routes
Route::post('auth/login', 'ApiUserController#authUser');
Route::post('auth/signup', 'ApiUserController#registerUser');
/* Persons */
Route::group(array('prefix' => 'people'), function() {
Route::get('{id}', 'ApiPeopleController#read');
Route::get('/', 'ApiPeopleController#read');
});
/* Events */
Route::group(array('prefix' => 'events'), function() {
Route::get('{id}', 'ApiEventsController#read');
Route::get('/','ApiEventsController#read');
});
});
Accessing the same url (http://localhost/site/public/api/auth/signup) from a rest client app on chrome does not give any errors, what could be wrong?
this is the angular code from my controller
$rootScope.show('Please wait..registering');
API.register({email: email, password: password})
.success(function (data) {
if(data.status == "success") {
console.log(data);
$rootScope.hide();
}
})
.error(function (error) {
console.log(error)
$rootScope.hide();
})
more angular code
angular.module('tedxph.API', [])
.factory('API', function ($rootScope, $http, $ionicLoading, $window) {
//base url
var base = "http://localhost/tedxph/public/api";
return {
auth: function (form) {
return $http.post(base+"/auth/login", form);
},
register: function (form) {
return $http.post(base+"/auth/signup", form);
},
fetchPeople: function () {
return $http.get(base+"/people");
},
fetchEvents: function() {
return $http.get(base+"/events");
},
}
});
It'd help to see the code you're using to make the angular request, as well as the header information from Chrome's Network -> XHR logger, but my first guess would be Angular is sending the AJAX request with the GET method instead of the POST method. Try changing Angular to send an explicit POST or change routes.php so auth/signup responds to both GET and POST requests.
Update looking at your screen shots, the AJAX request is returning an error 500. There should be information logged to either your laravel.log file or your PHP/webserver error log as to why the error is happening. My guess if your Angular request sends different information that your Chrome/REST-app does, and that triggers a code path where there's an error.
Fixed the problem, turns my controller was calling an undefined method in the controller class.
Renamed the method correctly and the request now works, thanks guys for the input.

Categories