CakePHP3 ~ CSRF-token mismatch on second function - javascript

I am currently developing an JavaScript reliable system that works with the CakePHP 3 framework as the back-end. All the actions in my panel are executed on the same page / url, it is a one-page-application. Currently I got a bunch of functions working and decided it was time to investigate the CSRF-component of CakePHP 3. When I enabled it I got the 400 error which indicated that no CSRF-token was given. After a lot of research I came to the solution that this is fixed with the FormMaker or with manually implementing the token in my JQuery ajax request. This worked so I decided to also implement it the next function. But as you may guess, this did not work. The second ajax request gives a CSRF-token mismatch. Please look at my code below:
My first request that actually works:
function getSectionData(controller, method = null) {
fetch("<?=$this->Url->build(['controller' => '']);?>" + "/" + controller + "/getSectionData", {
method: 'POST',
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": <?= json_encode($this->request->param('_csrfToken')); ?>
},
body: JSON.stringify({
method: method
})
})
.then((res) =>
res.json())
.then((data) => {
console.warn(data);
let response = data.response;
if (response.success == 1) {
// Action to do when data has been received
let method = response.method;
let rows = response.rows;
window[method](controller, rows);
} else if (response.success == 0) {
// Message when an error occures
}
})
.catch((err) => console.log(err))
}
Eventually the second function gets activated after less than a second:
function renderElement(element, id, data = null) {
let path = "<?= $this->request->here; ?>" + "/renderElement/" + element;
$('#' + id).load(path, {
data: data,
headers: {
"X-CSRF-Token": <?= json_encode($this->request->param('_csrfToken')); ?>
}
});
}
In this function I load a preset HTML element from another file which can contain data-elements, that is why it first requests data from the controller. I use the same headers as in the first function and the CSRF-token is present in the console. But it does not work and throws the mismatch error. I would like to know why it gives this error and how I can solve it, like, do I have to refresh it and how?
Note that I am using a version of CakePHP 3 before the 3.6 update which removed the CSRF component and added the CRSF middleware.
Thanks in advance.

The jQuery.load() function doesn't accept AJAX configuration parameters, the second argument is either a callback, or an object that defines the POST data to submit alongside the request.
You'll either have to switch to using a configurable AJAX request (for example as in your first snippet), or globally configure jQuery to set the required header for all AJAX requests:
$.ajaxSetup({
headers: {
'X-CSRF-Token': <?= json_encode($this->request->param('_csrfToken')); ?>
}
});
See also
https://api.jquery.com/load/
https://api.jquery.com/jQuery.ajaxSetup/

Related

How Can I set and send GET data when characters of an URI exceed 5,000

I am using a Laravel blade.
My code below happens error when the size of item of textarea is huge.
I am looking for a way to solve it.
Everyone. How should I send a huge text to server?
How should I modify my codes?
Blade(JavaScript)
function sendByGet()
{
var items = document.getElementById("item").value;
var param = "?items="+items+"&id={{$id}}";
var url = {{(url)}} + encodeURI(param);
let result = fetch(url);
result.then(response => response.json()).then(
responceObject =>{
}
}
}
Controller PHP
public function receivebyGet(Request $request)
{
$lineArray = array();
$slipData = explode("\n", $request->items);
Error
the date is replaces <huge_text> (Actual data is text(5000 characters))
phpbladeName?id=01:270 GET https://test.com/test/send_by_get?&item=<huge_text> 414
sendByGet # confirmation?id=01:270
onclick # confirmation?id=0:244
VM142:1 Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at phpbladeName?id=01
Move your data to the BODY
function sendByGet(url) {
const items = document.getElementById("item").value;
const param = encodeURI("?id={{$id}}");
fetch(url + param, {
method: 'GET',
headers: { 'Content-Type': 'plain/text' },
body: items,
})
.then(response => response.json())
.then( ... );
}
PHP Controller (assuming being Laravel)
public function receivebyGet(Request $request) {
$lineArray = array();
$slipData = explode("\n", $request->getContent());
...
}
Query size limit
As mentioned by Peter Krebs the maximum URL size (which includes the Query) is 2000 characters. So you cannot expect your system to reliably work if url is longher than 2000. Read more here.
GET body
As pointed out by Jabaa you should semantically choose the method, not technically. But this is something that has evolved over time (initially the GET body was supposed to be rejected/ignored) read more here. Hence you should consider it carefully and verify that all the parties involved (server, browser, proxies, cache) supports it properly. This is why oftentimes developers choose to violate semantics and use a POST method.

Why is my Ajax callback being processed too soon?

I have a general ajax function which I'm calling from loads of places in my code. It's pretty standard except for some extra debugging stuff I've recently added (to try to solve this issue), with a global 'ajaxworking' variable:
rideData.myAjax = function (url, type, data, successfunc) {
var dataJson = JSON.stringify(data),
thisurl = quilkinUrlBase() + url;
if (ajaxworking.length > 0) {
console.log(thisurl + ": concurrent Ajax call with: " + ajaxworking);
}
ajaxworking = thisurl;
$.ajax({
type: type,
data: dataJson,
url: thisurl,
contentType: "application/json; charset=utf-8",
dataType: "json",
async: true,
success: function (response) {
ajaxworking = '';
successfunc(response);
},
error: webRequestFailed
});
};
Now, there's one section of my code where a second ajax call is made depending on the result of the first:
getWebRides = function (date) {
var rideIDs = [];
var intdays = bleTime.toIntDays(date);
rideData.myAjax("GetRidesForDate", "POST", intdays, function (response) {
rides = response;
if (rides.length === 0) {
$('#ridelist').empty(); // this will also remove any handlers
qPopup.Alert("No rides found for " + bleTime.DateString(date));
return null;
}
$.each(rides, function (index) {
rideIDs.push(rides[index].rideID);
});
GetParticipants(rideIDs);
});
},
'GetParticipants' (which also calls 'myAjax') works fine - most of the time. But in another part of my code, 'GetWebRides' is itself called directly after another ajax call - i.e. there are 3 calls, each successive one depending on the previous. The 'top-level' call is as follows:
rideData.myAjax("SaveRide", "POST", ride, function (response) {
// if successful, response should be just a new ID
if (response.length < 5) {
// document re-arrangement code snipped here for brevity
getWebRides(date);
}
else {
qPopup.Alert(response);
}
});
so, only when there are three successive calls like this, I'm getting the 'concurrent' catch in the third one:
GetParticipants: concurrent call with GetRidesForDate
and (if allowed to proceed) this causes a nasty probem at the server with datareaders already being open. But why is this only occurring when GetParticipants is called as the third in the chain?
I see, after some research. that there are now other ways of arranging async calls, e.g. using 'Promises', but I'd like to understand what's going on here.
Solved this.
Part of the 'document re-arrangement code' that I had commented out for this post, was in fact calling another Ajax call indirectly (very indirectly, hence it took a long time to find).

Why do I get a MultiValueDictKeyError everytime I try to get the data from a POST request?

I'm currently learning in Django, and in this fetch request, I always get the same error. How could I avoid it?
topup.html
let data = new FormData();
data.append('amount', document.getElementById('amount').value);
data.append('csrfmiddlewaretoken', "{{ csrf_token }}");
var response = fetch('{% url "secret" %}', {
method: 'POST',
body: data,
credentials: 'same-origin',
})
views.py
def secret(request):
amount = request.POST["amount"]
error:
MultiValueDictKeyError at /secret
'amount'
Request Method: GET
Request URL: http://127.0.0.1:8000/secret
Django Version: 3.0.6
Exception Type: MultiValueDictKeyError
Exception Value:
'amount'
Exception Location: C:\Users\eric3\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\utils\datastructures.py in getitem, line 78
Python Executable: C:\Users\eric3\AppData\Local\Programs\Python\Python38-32\python.exe
Python Version: 3.8.2
Django Error
I would really appreciate some help.
Thanks!
You get MultiValueDictKeyError exception when opening that URL, because when request.POST["amount"] is evaluated, it founds no item within the POST dictionary with a key named 'amount'.
Apparently, you want secret view to be a view that is going to be reached only through ajax calls, not to be accessed by the user. If you don't want that, to get rid of that exception being raised when non-ajax requests reach the view, then do the following:
def secret(request):
amount = request.POST.get("amount")
# ... all your other code
Now it doesn't matter if your view is accessed by a user or by an ajax call, that exception is gone.
On the other hand, the documentation advises to use the canonical csrf token included in the cookies, when performing POST method requests through ajax. Make sure that you have put 'X-CSRFToken' within the HTTP request headers to do so.
The JavaScript code should look more or less as follows:
/* Create FormData instance and append the field that you want */
let data = new FormData();
data.append('amount', document.getElementById('amount').value);
/* Perform the fetch call */
fetch("{% url 'secret' %}", {
method: 'POST',
body: data,
headers: {'X-CSRFToken': csrftoken}
})
/* Do whatever you do when a response (successful or not) has been received */
.then(response => {
console.log(response);
})
/* Get csrf cookie */
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
As said in the docs, you can simplify a lot the process of getting the cookie by using the JavaScript Cookie Library

Angularjs firing request without request data

First of all my apologies if the question is repeated.
I am making ajax requests using $q service.
UtilityService.requestCall('/test/encrypt-data', {'json_string' : data_to_encrypt, 'encryption_key' : window.localStorage.getItem("Mi_Encryption_Key")})
.then(function(encrypt_response) {
var requestConoce = parseInt(window.localStorage.getItem("Mi_Cnonce")) + 1;
window.localStorage.setItem("Mi_Cnonce", requestConoce);
requestData['cnonce'] = requestConoce;
requestData['encrypted_data'] = encrypt_response.data;
return UtilityService.requestCall($scope.apiDetails.url, requestData, 'GET');
})
.then(function(api_response) {
var requestConoce = parseInt(window.localStorage.getItem("Mi_Cnonce")) + 1;
window.localStorage.setItem("Mi_Cnonce", requestConoce);
return UtilityService.requestCall('/test/decrypt-data', {'encrypted_string' : api_response.encrypted_data, 'encryption_key' : window.localStorage.getItem('Mi_Encryption_Key') });
})
.then(function(decrypt_response) {
$scope.serverResponse = JSON.stringify(decrypt_response);
return;
})
.catch(function(error) {
alert("Some Error");
})
MyApp.factory('UtilityService', ['$http', '$q', function($http, $q) {
return {
requestCall: function(requestUrl, requestData, methodType) {
var deferred = $q.defer();
var serverUrl = window.localStorage.getItem("Mi_Server_Url");
$http({
method: (methodType) ? methodType : "POST",
url: serverUrl + requestUrl,
data: requestData
})
.then(function(result) {
deferred.resolve(result.data);
},
function(error) {
deferred.reject(error);
});
return deferred.promise;
}
};}]);
I am making requests using the above code. It is working fine for the request "/test/encrypt-data"
But then request for $scope.apiDetails.url is not working. Request is made without any parameters But, I am sending all required parameters in requestData.
This code working for other (even I am sending the data) but not working for this request.
It seems angularjs requests two time for the same request once without data and other with data.
Please help for this strange issue. Please take a look on these images these are showing two different requests once with data and other without data.
First of all you are getting two requests because one of them is the OPTIONS call and one is the actual POST call. This is the normal behaviour and nothing to worry about.
The second request you are making is a GET request which cannot contain any POST-Data. This is just not supported by HTTP.
So depending on what the backend is expecting you could either turn that request into a POST request or you could add the data as GET parameters which is described here: in the Angular docs

How to override Backbone.sync so it adds the apikey and username at the end?

I am using backbone-tastypie, but I am having the toughest time getting it to work properly. In Tastypie, I am using ApiKeyAuthentication for my resources, so every ajax request, I need to append the apikey and username to the end of a request or send additional headers that add on the username and api key.
I am trying to remove a view and its model using backbone with the following code:
// Remove the goal update view from the DOM
removeItem: function() {
this.model.destroy({wait: true, success: function() {
console.log("success");
}, error: function() {
console.log("error");
}});
},
After the function executes, the browser tries to do a GET request on the following URL:
:8000/api/v1/update/2/
It does not include the api_key or username at the end, and it has a trailing slash at the end of the url. I think it is trying to use Backbone.oldSync to do the GET request. How would I make it so the sync does include the username/api key at the end and removes the trailing slash?
In all of the other requests, I have made it so the api key and username is appended to the end of the http request by adding the following code to backbone-tastypie:
if ( !resp && ( xhr.status === 201 || xhr.status === 202 || xhr.status === 204 ) ) { // 201 CREATED, 202 ACCEPTED or 204 NO CONTENT; response null or empty.
var location = xhr.getResponseHeader( 'Location' ) || model.id;
return $.ajax( {
url: location + "?" + "username=" + window.app.settings.credentials.username + "&api_key=" + window.app.settings.credentials.api_key,
success: dfd.resolve,
error: dfd.reject,
});
}
Let's explore the possibilities
Using headers
Backbone.sync still just uses jQuery ajax so you can override ajaxSend and use headers to send information along.
$(document).ajaxSend(function(e, xhr, options)
{
xhr.setRequestHeader("username", window.app.settings.credentials.username);
xhr.setRequestHeader("api_key", window.app.settings.credentials.api_key);
});
Using Ajax Options
If you need to send the information in just one or two locations, remember that the destroy, fetch, update and save methods are just shortcuts to the ajax caller. So you can add all jQuery ajax parameters to these methods as such:
// Remove the goal update view from the DOM
removeItem: function ()
{
this.model.destroy({
wait: true,
success: function ()
{
console.log("success");
},
error: function ()
{
console.log("error");
},
data:
{
username: window.app.settings.credentials.username,
api_key: window.app.settings.credentials.api_key
}
});
}
Overriding jQuery's ajax method
Depending on your needs, this might be the better implementation (note that this is no production code, you may need to modify this to fit your needs and test this before using it)
(function ($) {
var _ajax = $.ajax;
$.extend(
{
ajax: function (options)
{
var data = options.data || {};
data = _.defaults(data, {
username: window.app.settings.credentials.username,
api_key: window.app.settings.credentials.api_key
});
options.data = data;
return _ajax.call(this, options);
}
});
})(jQuery);
Just for future readers of this post, when you do a model.destroy() you can't pass any data because the delete request doesn't have a body, see this issue for more info:
https://github.com/documentcloud/backbone/issues/789

Categories

Resources