Menu Callback and CSRF How can I get a link with the correct csrf token for an internal path? Here is what I have:

in mymodule.routing.yml

mymodule.test:
  path: '/mymodule-test'
  defaults:
    _content: 'Drupal\mymodule\TestController::handleTest'
    _title: 'Handles test'
  requirements:
    _permission: 'access content'
    _csrf_token: 'TRUE'


building the link:

//$url = Url::fromRoute('mymodule.test');
//$settings['url'] = $url-&gt;toString();
$settings['url']=Drupal::url('mymodule.test');


the url contains the token in the query string as I expect, but it doesn't validate if I try to visit the link. When I edit CsrfTokenGenerator and dump out the token and the expected token, they do not match. All I can come up with is that the token is being generated for the page that I'm currently viewing rather than the route that I'm generating the url for.

Am I missing something? any ideas are appreciated!
Routes that need CSRF tokens have the _csrf_token: 'TRUE' route option. That's how URLs pointing to those routes automatically get a CSRF token.

So we need to look at \Drupal\Core\Access\RouteProcessorCsrf:

public function processOutbound($route_name, Route $route, array &amp;$parameters, BubbleableMetadata $bubbleable_metadata = NULL) {
  if ($route-&gt;hasRequirement('_csrf_token')) {
    …
    if (!$bubbleable_metadata) {
      $parameters['token'] = $this-&gt;csrfToken-&gt;get($path);
    }
    else {
      // Generate a placeholder and a render array to replace it.
      $placeholder = hash('sha1', $path);
      $placeholder_render_array = [
        '#lazy_builder' =&gt; ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
      ];

      // Instead of setting an actual CSRF token as the query string, we set
      // the placeholder, which will be replaced at the very last moment. This
      // ensures links with CSRF tokens don't break cacheability.
      $parameters['token'] = $placeholder;
      $bubbleable_metadata-&gt;addAttachments(['placeholders' =&gt; [$placeholder =&gt; $placeholder_render_array]]);
    }
  }
}


So there we can see that if $bubbleable_metadata is NULL, then we generate the CSRF token right away. $bubbleable_metadata being NULL signals to the CSRF route processor that the URL is being generated in a context where no cacheability metadata and attachments can be bubbled (e.g. when generating a URL to be sent in an e-mail). The downside is that the HTML containing this URL will then be uncacheable (well, very poorly cacheable), because the CSRF token is depends on the session.

That's why in the other case, where $bubbleable_metadata is a BubbleableMetadata instance, that signals to the CSRF route processor that the URL is being generated in a context where cacheability metadata and attachments can be bubbled (e.g. when generating a URL to be rendered in some HTML). So there we generate a placeholder token that will be replaced with the CSRF token, and specify a "placeholder" attachment that will ensure that placeholder token is replaced with a CSRF token at the last possible moment. Consequently, the HTML that this URL ends up in is still perfectly cacheable, because the URL is cacheable.

Now that problem has been confirmed. If this happens in a preprocess function, as the following one, then you end up with $variables['url'] = GeneratedUrl(…). Twig ought to bubble the cacheability metadata and attachment metadata in that object, but it does NOT, in all versions of Drupal 8 before 8.1.2. We're fixing this for Drupal 8.1.2 at https://www.drupal.org/node/2575519.

function hook_preprocess_HOOK(&amp;$variables) {
  $variables['url'] = \Drupal::url('mymodule.test');
}