In the documentation is described how to exclude routes from CSRF protection, but I would prefer to include them. Is there a way to add the CSRF token into a hidden field, what name should it have?
Scanning through source code I only see a header that is added to AJAX calls, does this mean than in the current version regular POST calls aren't possible to make safe?
We could have allowed a hidden field to be used, but a hidden field is less safe than a header because a third party phishing site is permitted to submit regular POST forms, but it is not permitted to add HTTP headers to requests. So the standard protection covers you in two ways: (1) the attacker can't access the CSRF cookie, and (2) the attacker can't add a header either because only JavaScript can add headers and JavaScript is subject to the same-origin policy.
Thus using a jQuery form submission rather than "plain vanilla" POST is best practice for ApostropheCMS.
However jQuery form submissions are very easy anyway and they work better; you don't have to render an entirely new page for the user, for one thing.
Here is a simple example of jQuery code to enhance an ordinary HTML form:
$(function() {
var $myForm = $('.my-form');
$myForm.on('submit', function() {
$.post($myForm.attr('action'), $myForm.serialize(), function() {
// It worked, now display a thank you message, or navigate somewhere
}).fail(function() {
// An error was received, show the user a message etc.
});
// IMPORTANT: prevent the traditional submission
return false;
});
});
Here I am pulling the URL to POST the form to from the action attribute but you don't have to do that, you can use any URL that is set up to receive a POST form submission.
Note that I did not do anything special to get AJAX protection here. As long as you use jQuery's AJAX mechanisms (including $.post), it is automatic with ApostropheCMS.
You can fill in my success and fail functions here with code to show or hide messages already embedded in your page, etc.
Edit: here is a possible workaround to make it work the way you want it to.
// Browser-side JavaScript
$('form').each(function() {
$(this).append('<input type="hidden" name="xsrf-token" value="' + $.cookie(apos.csrfCookieName) + '" />');
});
Now, in lib/modules/your-module/index.js, you can supply middleware to put this hidden field where Apostrophe expects it:
self.expressMiddleware = {
when: 'beforeRequired',
middleware: function(req, res, next) {
if (!(req.body && req.body['xsrf-token'])) {
return next();
}
req.headers['X-XSRF-TOKEN'] = req.body['xsrf-token'];
return next();
}
};
Here is what I'm trying to accomplish (IE 9+, Chrome, FF, Safari) without the use of JQuery:
Make an http POST call to my API endpoint with some data
Server dynamically generates a PDF and returns the PDF as a binary attachment
Browser does default download behavior and downloads the PDF without refreshing the page
Basically I want to get the behavior similar to <a href="test.pdf"> but for a dynamically generated PDF after making a POST call instead of a GET call.
I've tried lots of different things, but they either didn't work cross browser (such as using $window.open() with a blob URL), were blocked by popup blockers (any $window call outside of the click scope), or didn't cause the PDF to be automatically downloaded (any $http POST solution).
I finally found one solution that seems to work which creates a form using javascript and submits it.
var form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', myurl);
var params = {foo: 'bar'};
for(var key in params) {
if(params.hasOwnProperty(key)) {
var hiddenField = document.createElement('input');
hiddenField.setAttribute('type', 'hidden');
hiddenField.setAttribute('name', key);
hiddenField.setAttribute('value', params[key]);
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
form.submit();
This successfully accomplishes the 3 steps above, but now I've run into a new problem. There is no way to determine when the PDF file has been successfully downloaded. This is preventing me from removing the form and from displaying a friendly 'Please wait...' message to the user. There is also the additional problem that submitting the form cancels any outstanding ajax requests as well which isn't optimal.
I have full control over both the server and the client, so what's the best way to fix this? I don't want to have to save the PDF on the server so passing back a url and doing a second GET request from the client won't work in this case. Thanks!
You can make an server response behave as a download by applying some HTTP headers:
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="SOME_NAME.pdf"
Content-Transfer-Encoding: binary
If you're initiating the download through JS only (instead of having the user click a download link), then check out this question for some caveats.
Update: Syntax for POST
Better Update: Form solution with iframe target
You can detect that your server-side script has finished (and subsequently, that the download is ready to begin) by having the form target an iframe. I believe this should also fix the issue of cancelling outstanding Ajax calls, but I'm not certain. Here is the code to do it (just stick this into your code example after the for loop and before document.body.appendChild(form);):
var frame = document.createElement('iframe');
frame.setAttribute('id', 'pdfFrame');
frame.onload = function(){
document.body.removeChild(form);
document.body.removeChild(frame);
alert('Download ready!');
}
document.body.appendChild(frame);
form.setAttribute('target', 'pdfFrame');
You can replace my alert with your code to remove the 'Please wait...'.
I need to display on my Web page a simple text string returned from a REST service. I am currently using an XMLHttpRequest:
<div id="returnedText"></div>
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.status == 200 && xhr.readyState == 4) {
document.getElementById("returnedText").innerHTML=xhr.responseText;
}
};
xhr.open("GET",url,true);
xhr.send(null);
</script>
Isn't there a lighter way? I considered using a script tag but the Web service in question doesn't support JSONP. I also did a naive attempt with an iframe (putting the REST url as src) but it didn't work.
I did another attempt with iframes and actually this works fine:
<iframe src="url"></iframe>
Where url is the REST service call.
I must have done something wrong the first time (maybe an authentication issue).
Well the iframe route is clunky since you'd be loading the REST response into it and then reaching into it via JS to get the response. What's more, it would cause a visible load in the browser's address bar area. AJAX came along to do away with the iframe hack :)
JSON-P requires about as much setup as AJAX and if your server doesn't support the callback, that's a none starter.
AJAX needn't be thought heavy. Kick it into its own utility function or, even better, use a library, which makes requests like these do'able in one line. jQuery example:
$.get('some/path').done(function(response) { /* do something */ });
Page one contains an HTML form. Page two - the code that handles the submitted data.
The form in page one gets submitted. The browser gets redirected to page two. Page two handles the submitted data.
At this point, if page two gets refreshed, a "Confirm Form Resubmission" alert pops up.
Can this be prevented?
There are 2 approaches people used to take here:
Method 1: Use AJAX + Redirect
This way you post your form in the background using JQuery or something similar to Page2, while the user still sees page1 displayed. Upon successful posting, you redirect the browser to Page2.
Method 2: Post + Redirect to self
This is a common technique on forums. Form on Page1 posts the data to Page2, Page2 processes the data and does what needs to be done, and then it does a HTTP redirect on itself. This way the last "action" the browser remembers is a simple GET on page2, so the form is not being resubmitted upon F5.
You need to use PRG - Post/Redirect/Get pattern and you have just implemented the P of PRG. You need to Redirect. (Now days you do not need redirection at all. See this)
PRG is a web development design pattern that prevents some duplicate form submissions which means, Submit form (Post Request 1) -> Redirect -> Get (Request 2)
Under the hood
Redirect status code - HTTP 1.0 with HTTP 302 or HTTP 1.1 with HTTP 303
An HTTP response with redirect status code will additionally provide a URL in the location header field. The user agent (e.g. a web browser) is invited by a response with this code to make a second, otherwise identical, request to the new URL specified in the location field.
The redirect status code is to ensure that in this situation, the web user's browser can safely refresh the server response without causing the initial HTTP POST request to be resubmitted.
Double Submit Problem
Post/Redirect/Get Solution
Source
Directly, you can't, and that's a good thing. The browser's alert is there for a reason. This thread should answer your question:
Prevent Back button from showing POST confirmation alert
Two key workarounds suggested were the PRG pattern, and an AJAX submit followed by a scripting relocation.
Note that if your method allows for a GET and not a POST submission method, then that would both solve the problem and better fit with convention. Those solutions are provided on the assumption you want/need to POST data.
The only way to be 100% sure the same form never gets submitted twice is to embed a unique identifier in each one you issue and track which ones have been submitted at the server. The pitfall there is that if the user backs up to the page where the form was and enters new data, the same form won't work.
There are two parts to the answer:
Ensure duplicate posts don't mess with your data on the server side. To do this, embed a unique identifier in the post so that you can reject subsequent requests server side. This pattern is called Idempotent Receiver in messaging terms.
Ensure the user isn't bothered by the possibility of duplicate submits by both
redirecting to a GET after the POST (POST redirect GET pattern)
disabling the button using javascript
Nothing you do under 2. will totally prevent duplicate submits. People can click very fast and hackers can post anyway. You always need 1. if you want to be absolutely sure there are no duplicates.
You can use replaceState method of JQuery:
<script>
$(document).ready(function(){
window.history.replaceState('','',window.location.href)
});
</script>
This is the most elegant way to prevent data again after submission due to post back.
Hope this helps.
If you refresh a page with POST data, the browser will confirm your resubmission. If you use GET data, the message will not be displayed. You could also have the second page, after saving the submission, redirect to a third page with no data.
Well I found nobody mentioned this trick.
Without redirection, you can still prevent the form confirmation when refresh.
By default, form code is like this:
<form method="post" action="test.php">
now, change it to
<form method="post" action="test.php?nonsense=1">
You will see the magic.
I guess its because browsers won't trigger the confirmation alert popup if it gets a GET method (query string) in the url.
The PRG pattern can only prevent the resubmission caused by page refreshing. This is not a 100% safe measure.
Usually, I will take actions below to prevent resubmission:
Client Side - Use javascript to prevent duplicate clicks on a button which will trigger form submission. You can just disable the button after the first click.
Server Side - I will calculate a hash on the submitted parameters and save that hash in session or database, so when the duplicated submission was received we can detect the duplication then proper response to the client. However, you can manage to generate a hash at the client side.
In most of the occasions, these measures can help to prevent resubmission.
I really like #Angelin's answer. But if you're dealing with some legacy code where this is not practical, this technique might work for you.
At the top of the file
// Protect against resubmits
if (empty($_POST)) {
$_POST['last_pos_sub'] = time();
} else {
if (isset($_POST['last_pos_sub'])){
if ($_POST['last_pos_sub'] == $_SESSION['curr_pos_sub']) {
redirect back to the file so POST data is not preserved
}
$_SESSION['curr_pos_sub'] = $_POST['last_pos_sub'];
}
}
Then at the end of the form, stick in last_pos_sub as follows:
<input type="hidden" name="last_pos_sub" value=<?php echo $_POST['last_pos_sub']; ?>>
Try tris:
function prevent_multi_submit($excl = "validator") {
$string = "";
foreach ($_POST as $key => $val) {
// this test is to exclude a single variable, f.e. a captcha value
if ($key != $excl) {
$string .= $key . $val;
}
}
if (isset($_SESSION['last'])) {
if ($_SESSION['last'] === md5($string)) {
return false;
} else {
$_SESSION['last'] = md5($string);
return true;
}
} else {
$_SESSION['last'] = md5($string);
return true;
}
}
How to use / example:
if (isset($_POST)) {
if ($_POST['field'] != "") { // place here the form validation and other controls
if (prevent_multi_submit()) { // use the function before you call the database or etc
mysql_query("INSERT INTO table..."); // or send a mail like...
mail($mailto, $sub, $body); // etc
} else {
echo "The form is already processed";
}
} else {
// your error about invalid fields
}
}
Font: https://www.tutdepot.com/prevent-multiple-form-submission/
use js to prevent add data:
if ( window.history.replaceState ) {
window.history.replaceState( null, null, window.location.href );
}
I just finished the basic design structure for my contact page without flash; it's located here.
Can anyone suggest the best approach for making a confirmation script (inside a DIV) without reloading the page (preferably with jQuery). I want to replace the content in the main WRAP with new content (just text) confirming the email was received.
Any suggestions?
First of all I don't believe you can notify the user that the mail was actually received (at least not in a trivial way). But you can notify that it was sent.
For this with jQuery you can send the contact info via AJAX and then show the response in the DIV.
May be something like this:
$.ajax({
type: "POST",
url: "sendMail.php",
data: $('#contactForm').serialize(),
success: function(msg){
$("#responseDiv").html(msg).show();
}
});
Of course this is assuming that your server sends the for with "sendForm.php" and that your contact form is wrapped with a <form> with "contactForm" as an id.
The server should respond with the text to show within the div. If the message was sent or not.
Hope this helps.
References:
Ajax help for jQuery