Stripe field won't load on multi-page forms when `asyncCsrfInputs=true`
What happened?
So we have some forms that are multi-page and include a payment field for Stripe on the last page (re: Place Stripe field on Last Page.
While we are caching a much of the site, we actually don't cache modules that render a form. That said, if we don't set asyncCsrfInputs to true for the site than Craft will send a no cache header on every request that uses a CSRF token.
We've accommodated this by implementing the form caching instructions noted here with some alterations, but accomplishing the same effect. Difference are as follows:
- Craft includes a built in action for fetching CSRF info already—no need to create an uncached twig template to render that JSON.
window.$app.csrf = fetch('/actions/users/session-info', {
headers: { 'Accept': 'application/json' },
}).then(res => res.json()).then(res => ({
name: res.csrfTokenName,
value: res.csrfTokenValue,
}))
- CSRF data is appended to FormData before submit:
document.addEventListener('DOMContentLoaded', async () => {
const csrf = await window.$app.csrf
const forms = document.querySelectorAll('form')
for (const form of forms) {
form.addEventListener('freeform-ajax-before-submit', event => {
event.data.append(csrf.name, csrf.value)
})
}
})
That said, we have also tested what is in the docs with the following replacing the freeform-ajax-before-submit event listener in the DOMContentLoaded handler above:
const csrfInput = form.querySelector(`input[name=${csrf.name}]`)
if (csrfInput instanceof HTMLInputElement) {
csrfInput.value = csrf.value
}
However this change does not fix the issue either.
Multi-page forms in general do appear to work with the above setup, but when a Stripe payment field is included on one of these subsequent pages the call to /freeform/payments/stripe/payment-intents returns a 400 Bad Request response. Logs indicate that the call is unable to verify the CSRF token.
This does appear to be an issue on the JS side of things with Stripe, but including the PHP stack track below just in case.
Errors and Stack Trace (if available)
2025-05-06 17:02:39 [web.ERROR] [yii\web\HttpException:400] yii\web\BadRequestHttpException: Unable to verify your data submission. in /var/www/html/vendor/yiisoft/yii2/web/Controller.php:221
Stack trace:
#0 /var/www/html/vendor/craftcms/cms/src/web/Controller.php(176): yii\web\Controller->beforeAction()
#1 /var/www/html/vendor/yiisoft/yii2/base/Controller.php(176): craft\web\Controller->beforeAction()
#2 /var/www/html/vendor/solspace/craft-freeform/packages/plugin/src/controllers/BaseApiController.php(15): yii\base\Controller->runAction()
#3 /var/www/html/vendor/yiisoft/yii2/base/Module.php(552): Solspace\Freeform\controllers\BaseApiController->runAction()
#4 /var/www/html/vendor/craftcms/cms/src/web/Application.php(361): yii\base\Module->runAction()
#5 /var/www/html/vendor/yiisoft/yii2/web/Application.php(103): craft\web\Application->runAction()
#6 /var/www/html/vendor/craftcms/cms/src/web/Application.php(329): yii\web\Application->handleRequest()
#7 /var/www/html/vendor/yiisoft/yii2/base/Application.php(384): craft\web\Application->handleRequest()
#8 /var/www/html/web/index.php(12): yii\base\Application->run()
#9 {main} {"memory":7872312,"exception":"[object] (yii\\web\\BadRequestHttpException(code: 0): Unable to verify your data submission. at /var/www/html/vendor/yiisoft/yii2/web/Controller.php:221)"}
How can we reproduce this?
- Configure Craft with
asyncCsrfInputs => true - Create a multi-page form with a Stripe field included on any page but the first
- Submit the first page to render the page including the Stripe field.
Freeform Edition
Pro
Freeform Version
5.10.11
Craft Version
5.7.4
When did this issue start?
- [ ] Unsure
- [ ] Fresh install of Freeform
- [x] After upgrading from older Freeform version
- [x] After upgrading from older Craft version
- [ ] After a change to hosting configuration
Previous Freeform Version
3.13.38
Hi @corneliusio,
I'm sorry for the trouble you are experiencing. I will have a developer look into this shortly and get back to you. 🙂
Hi @corneliusio,
Sorry for the trouble you're experiencing. We have now implemented an auto-fetch mechanism for CSRF tokens within any ajax requests used by freeform.
This will remove the necessity to do any sort of manual updating of csrf tokens within cached pages and will work out of the box, so you won't need to write any custom JS logic yourself.
We do need to perform some tests before we can release, you you can test out these changes by setting your composer.json freeform dependency to use dev-fix/SFT-2077-csrf-input as the version.
{
"require": {
// ... other dependencies
"solspace/craft-freeform": "dev-fix/SFT-2077-csrf-input"
}
}
@gustavs-gutmanis would you mind making the auto-fetch of CSRF tokens configurable in settings, so it can be disabled / enabled? We have our own logic to handle this, so if this was enabled by default it will lead to duplicate requests. Many thanks.
@croxton That's a fair point. We'll make sure it's disabled by default.
Appreciate your feedback 🙂
We've now added a new setting in Freeform 5.11+ for this.
When asyncCsrfInputs/CRAFT_ASYNC_CSRF_INPUTS is set to true, a new setting will appear under Settings -> Form Behavior called CSRF Token Refresh Method.
It's set to Never (original pre-5.11 behavior) by default, but you can then change this to one of the other two options and it should resolve your issue. 🙂