CSRF access checking

Last updated on
4 December 2025

This documentation needs review. See "Help improve this page" in the sidebar.

Cross-Site Request Forgery (CSRF) is when a privileged user is tricked into making a request by following a link from an unrelated web site (or some other source, such as email). Any requests that perform actions, such as creating, updating, or deleting content, need protection from CSRF. They can be protected by requiring the privileged user to submit a confirmation form or by requiring a token based on  the user's session. Only the current site can provide a valid token.

For a more complete description of CSRF, see the OWASP page on CSRF or the Wikipedia page on CSRF.

CSRF protection is integrated into the routing access system. It should be used for any URLs that perform actions or operations that do not use a form callback. Based on the logic observed in Drupal core, if the controller method handling your custom route returns any kind of \Symfony\Component\HttpFoundation\Response object (there are dozen of kinds), you will probably need to protect your route from CSRF.

Forms

The Form API adds form_token as a hidden value to every form, then validates that value when the form is submitted. This protects against CSRF.

The _csrf_token requirement

When defining a route, you can add the requirement _csrf_token: 'TRUE'. This does two things:

  1. Add a CSRF token to the query string when rendering a URL object based on the route. The token is valid only for the current user.
  2. Validate that token before responding to the route. If the token does not validate, then return an Access Denied (403) response.

Example:

# example.routing.yml

example:
  path: '/example'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::content'
  requirements:
    # Be careful: the value is a string, not a boolean.
    _csrf_token: 'TRUE'

To generate the URL for the _csrf_token: 'TRUE', use the code snippet below. The CSRF token will be added and validated implicitly.

$url = Url::fromRoute(
  'node_test.report',
  ['node' => $entity->id()],
);

Query parameters are not considered when generating URLs in this manner. You can manually generate a CSRF token to overcome this.

$url = Url::fromRoute('node_test.report', ['node' => $entity->id]);
// $query may already have other parameters.
$query['token'] = \Drupal::service('csrf_token')->get($url->getInternalPath());
$url->setOption('query', $query);

Explicitly add and validate tokens

In order for the token to be added, the link must be generated using the url_generator service via route name rather than as a manually constructed path.

$url = Url::fromRoute(
  'node_test.report',
  ['node' => $entity->id()],
  ['query' => [
    'token' => \Drupal::getContainer()->get('csrf_token')->get("node/{$entity->id()}/report")
  ]]);

API reference [11.0]: CsrfTokenGenerator::get

To validate token manually (that is, without adding _csrf_token: 'TRUE' to the route definition), use the token and value used for generating it in the controller:

// Validate $token from GET parameter.
\Drupal::service('csrf_token')->validate($token, "node/{$entity->id()}/report");

Let Drupal render the URL object

Think twice before generating the full URL, including the token, as a string with URL::toString(). What do you plan to do with that string? Will it be handled securely? Will it be properly cached?

The _csrf_confirm_form_route option

When a route requires _csrf_token: 'TRUE' it can add the _csrf_confirm_form_route option. In this case, Drupal will redirect to the specified route if the CSRF token does not validate.

Example:

# user.routing.yml

user.logout:
  path: '/user/logout'
  defaults:
    _controller: '\Drupal\user\Controller\UserController::logout'
  requirements:
    _user_is_logged_in: 'TRUE'
    _csrf_token: 'TRUE'
  options:
    _csrf_confirm_form_route: 'user.logout.confirm'

See New route option for redirecting when access is denied to a CSRF protected route.

The _csrf_request_header_token requirement

Setting _csrf_request_header_token: 'TRUE' in the route definition requires validation just like _csrf_token: 'TRUE', but

  1. The CSRF token is not added when the URL object is rendered.
  2. The CSRF token should be provided as the X-CSRF-Token HTTP header, not as a query parameter.
  3. The CSRF validation always succeeds for anonymous users, so the _csrf_request_header_token requirement should be used along with another requirement, such as a permission.

This requirement will not apply to 'GET', 'HEAD', 'OPTIONS', or 'TRACE' requests.
See CSRF token route protection moved out of the REST module to be available to other core systems and contrib.

The system.csrftoken route

The system.csrftoken route provides the path /session/token, which can be used to get the CSRF token for the current user.

If an AJAX process needs access to a CSRF-protected route, then it can get the token from /session/token. For a code example, see GET an Item and then UPDATE Item with CSRF token in the documentation for the RESTful Web Services module.

Anonymous Users

Currently the _csrf_token check fails for users without an active session, which includes most anonymous users. See: #2730351: CSRF check always fails for users without a session

Tags

Help improve this page

Page status: Needs review

You can: