CSRF access checking
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:
- Add a CSRF token to the query string when rendering a
URLobject based on the route. The token is valid only for the current user. - 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
- The CSRF token is not added when the
URLobject is rendered. - The CSRF token should be provided as the
X-CSRF-TokenHTTP header, not as a query parameter. - 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
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion
Still on Drupal 7? Security support for Drupal 7 ended on 5 January 2025. Please visit our Drupal 7 End of Life resources page to review all of your options.