Problem/Motivation
The Cloudflare Turnstile widget generates a single-use token upon solving a challenge. In Drupal, many forms can potentially use AJAX for partial updates or dynamic behaviors. For example: In a project I'm working on the address module updates the city/state based on postal code, or dependent dropdowns. This behavior is consistent with the problem reported in issue https://www.drupal.org/project/turnstile/issues/3537533, where AJAX validations from other contrib modules cause the same token "burning" problem.
When an AJAX event is triggered, Drupal's Form API processes the form and executes the validation callbacks to rebuild the form state. During this process, the turnstile_captcha_validation function is executed, causing the module to validate the token against Cloudflare's API immediately. As a result, the token is marked as "used" by Cloudflare. When the user finally clicks the "Submit" button, the form is sent with the same token, which Cloudflare now rejects as a duplicate (timeout-or-duplicate — "The response parameter has already been validated before"). This forces the user to solve the captcha again — if the widget is visible — or, more critically, blocks the submission entirely if the widget is invisible/interactive (The answer you entered for the CAPTCHA was not correct.)
Proposed resolution
Introduce a "first layer of defense" to distinguish between a final form submission and a partial AJAX rebuild. This ensures that the token is only validated with Cloudflare when a legitimate submission occurs. Potentially, a "second layer" could be created to verify that the element triggering the validation is a submit button, but that's outside the scope of this issue.
I will create a Merge request with the solution I have applied in one of the projects I am working on, which so far seems to be working correctly. Thanks @greatmatter for creating this module.
Issue fork turnstile-3576146
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
dimaro commentedComment #5
greatmatter commentedThank you, @dimaro! I've merged this into dev; let's let it sit for a week or two there, after which we'll mark it as RTBC, and release it.
Comment #6
greatmatter commentedComment #8
dimaro commentedHello @greatmatter,
Thank you for the update and the new release. I would like to ask you something, if it's not too much trouble: Could you update the contribution record to give credit to both myself and the organization that supported me in creating this issue? If the contribution had been made in my free time, I wouldn't give it so much importance. Thanks in advance!
Comment #9
greatmatter commentedDone!
Comment #11
greatmatter commented