1.1.13 -> 1.1.14 or 1.1.15 Turnstile failing to load

Created on 17 March 2025, 20 days ago

We have been using this module successfully up until 1.1.13. Since the update the capture for turnstile no longer loads. This has been thoroughly testes on Drupal 10.3 + and Drupal 11 website we run and maintain.

So something invalidate here:
https://git.drupalcode.org/project/turnstile/-/compare/1.1.13...1.1.14?f...

I assume the attach has something to do with it?

πŸ› Bug report
Status

Active

Version

1.1

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States glynster

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @glynster
  • πŸ‡ΊπŸ‡ΈUnited States greatmatter

    What error (if any) are you receiving?

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    I am debugging at the moment to try and be useful. There are no console errors. However here are some observations:

    Locally it does load fine.
    On production it does not.
    Markup wise I can see

    <div class="cf-turnstile" data-sitekey="################" data-theme="light" data-size="normal" data-language="auto" data-retry="auto" interval="8000" data-appearance="always"></div>

    but on version 1.1.13 the markup is:

    <div class="cf-turnstile" data-sitekey="################" data-theme="light" data-size="normal" data-language="auto" data-retry="auto" interval="8000" data-appearance="always"><div><input type="hidden" name="cf-turnstile-response" id="cf-chl-widget-b5dkd_response" value="TOKEN"></div></div>

  • πŸ‡ΊπŸ‡ΈUnited States greatmatter

    I think I got it--I've just pushed a hotfix to 1.1.16

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    So it seems this actually fixes the issue:

    1️⃣ Run This in Browser Console to Force a Reset

    document.querySelectorAll('.cf-turnstile').forEach(el => {
    turnstile.remove(el);
    });
    turnstile.render('.cf-turnstile', { sitekey: "######" });

    2. Modify the Form Rendering Code to Prevent Duplicate Initialization

    To avoid this problem, modify your Drupal form rendering:

    $form['turnstile_widget'] = [
      '#markup' => '<div id="turnstile-container" class="cf-turnstile" data-sitekey="' . turnstile_site_key() . '"></div>',
    ];
    
    $form['#attached']['drupalSettings']['turnstile'] = [
      'callback' => 'turnstileSuccess',
      'errorCallback' => 'turnstileError',
    ];
    
    $form['#attached']['library'][] = 'turnstile/turnstile';
    

    Then add this script in turnstile.ajax.js to remove any existing Turnstile instances before rendering a new one:

    document.addEventListener("DOMContentLoaded", function () {
      const turnstileContainer = document.querySelector('.cf-turnstile');
      if (turnstileContainer) {
        turnstile.remove(turnstileContainer);
        turnstile.render(turnstileContainer, {
          sitekey: drupalSettings.turnstile.sitekey
        });
      }
    });
  • πŸ‡ΊπŸ‡ΈUnited States greatmatter

    Would you mind letting me know if 1.1.16 fixes it for you?

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    @greatmatter thanks so much for being so responsive. I just deployed with your new hotfix and sadly I get the same results, fails to load/initialize.

  • πŸ‡ΊπŸ‡ΈUnited States greatmatter

    How odd--I've tested it across our sites--would you mind clearing your caches (including edge caches, if any)?

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    No luck on my end We have cleared caches Drupal, Varnish, Cloudflare, etc. The only fix is this:

    turnstile.render('.cf-turnstile', { sitekey: "#################" });

    With the current version or revert to 1.1.13.

  • πŸ‡ΊπŸ‡ΈUnited States greatmatter

    Please try resaving your keys--I'm wondering if something weird happened with the upgrading/downgrading.

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    I wish I could have good news and it be that simple but it is not.

    This seems to work:

    /**
     * @file
     * JavaScript behaviors for Turnstile.
     */
    (function ($, Drupal, once) {
      'use strict';
    
      /**
       * Ensures Turnstile renders correctly on page load & AJAX events.
       */
      Drupal.behaviors.turnstileAutoRender = {
        attach: function (context) {
          once('turnstile-init', '.cf-turnstile', context).forEach((el) => {
            turnstile.render(el);
          });
        }
      };
    
      /**
       * Re-renders Turnstile when an AJAX request updates the form.
       */
      Drupal.AjaxCommands.prototype.turnstileRender = function () {
        $('.cf-turnstile').each(function(){
          turnstile.render(this);
        });
      };
    
    })(jQuery, Drupal, once);
  • πŸ‡ΊπŸ‡ΈUnited States glynster
  • πŸ‡ΊπŸ‡ΈUnited States greatmatter

    I cannot seem to replicate this issue. The code you sent will likely break the Form/AJAX functionality (using EventSubscriber) introduced in 1.1.10.

    I'm wondering if, for some reason, the

    isn't getting automatically called by the script. Do you have multiple CAPTCHAs on the same page?
  • πŸ‡ΊπŸ‡ΈUnited States glynster

    Only 1, nothing fancy going on here. These are just basic webforms and GIn Login pages.

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    Okay well all our sites are behind Cloudflare and as soon as we disable RocketLoader it works. But we have always used this service. Since the change this is now conflicting it seems.

  • πŸ‡ΊπŸ‡ΈUnited States keiserjb

    I was getting cookie errors in the Chrome console.

    Reading cookie in cross-site context may be impacted on Chrome
    Cookies with the SameSite=None; Secure and not Partitioned attributes that operate in cross-site contexts are third-party cookies. Chrome is moving towards a new experience that allows users to choose to browse without third-party cookies.

    Learn more from the linked article about preparing your site to avoid potential breakage.

    It listed multiple cookies from the the cloudflare domain.

    Thanks for the tip about Rocket Loader in Cloudflare. I turned that off and it started working.

  • πŸ‡ΊπŸ‡ΈUnited States keiserjb

    Rolling back to 1.1.13 works as well.

  • πŸ‡ΊπŸ‡ΈUnited States greatmatter

    Hm. I wonder why it's working in our sites. We're behind Cloudflare, but don't use RocketLoader. We also have SameSite=Lax - so I'm wondering if that plays a role. I'm open to ideas of how to make this work.

    @keiserjb - what errors are you getting in the console?

    The big change between versions was how the main Cloudflare Turnstile script was being included. We could add to the turnstile_library_info_build() the defer and async attributes--would you mind testing that to let me know if that changes anything? I'm not going to make a patch before knowing if it works for you.

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    @greatmatter you are referring to the services file right?

    parameters:
      session.storage.options:
        cookie_samesite: Lax

    We already have this set on our sites, so we can rule that one out.

    I can confirm that updating the widget does resolve the issue

    /**
     * Build the Turnstile captcha form.
     *
     * @return mixed
     *   The return value.
     */
    public function getWidget($validation_function) {
      // Add Turnstile script with async and defer attributes.
      $widget['form']['#attached']['html_head'][] = [
        [
          '#tag' => 'script',
          '#attributes' => [
            'src' => 'https://challenges.cloudflare.com/turnstile/v0/api.js',
            'async' => 'async',
            'defer' => 'defer',
          ],
        ],
        'turnstile_api',
      ];
    
      // Captcha requires TRUE to be returned in solution.
      $widget['solution'] = TRUE;
      $widget['captcha_validate'] = $validation_function;
      $widget['form']['captcha_response'] = [
        '#type' => 'hidden',
        '#value' => self::EMPTY_CAPTCHA_RESPONSE,
      ];
    
      // Allows the CAPTCHA to be displayed on cached pages.
      $widget['cacheable'] = TRUE;
    
      // Ensure the widget is properly rendered.
      $widget['form']['turnstile_widget'] = [
        '#markup' => '<div' . $this->getAttributesString() . '></div>',
      ];
      
      return $widget;
    }
    
  • πŸ‡ΊπŸ‡ΈUnited States glynster

    Yes this suggestions resolves the issue as well

    /**
     * Implements hook_library_info_build().
     */
    function turnstile_library_info_build() {
      $config = \Drupal::config('turnstile.settings');
      $turnstile_src = $config->get('turnstile_src');
    
      $libraries = [];
    
      $libraries['turnstile.remote'] = [
        'js' => [
          $turnstile_src => [
            'type' => 'external',
            'attributes' => [
              'defer' => TRUE,
              'async' => TRUE,
            ],
          ],
        ],
      ];
    
      return $libraries;
    }
    
  • πŸ‡ΊπŸ‡ΈUnited States keiserjb

    Changing hook_library_info_build works for me as well with 1.1.16.

    I had trouble replicating this problem after discovery last week as it still worked locally and on my dev site. Today, I was able to find out how to break Turnstile on my dev site by adding a domain. Then I got the cross-site cookie errors just like production. The js alterations fix it though.

  • πŸ‡ΊπŸ‡ΈUnited States greatmatter

    Ok, it's all fixed in 1.1.17. Thanks, everyone!

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    Rockstar thank you so much for pushing this fix.

  • πŸ‡ΊπŸ‡ΈUnited States greatmatter
Production build 0.71.5 2024