3 namespace Drupal\Core\Access;
5 use Drupal\Core\Session\AccountInterface;
6 use Drupal\Core\Session\SessionConfigurationInterface;
7 use Symfony\Component\Routing\Route;
8 use Symfony\Component\HttpFoundation\Request;
11 * Access protection against CSRF attacks.
13 class CsrfRequestHeaderAccessCheck implements AccessCheckInterface {
16 * A string key that will used to designate the token used by this class.
18 const TOKEN_KEY = 'X-CSRF-Token request header';
21 * The session configuration.
23 * @var \Drupal\Core\Session\SessionConfigurationInterface
25 protected $sessionConfiguration;
28 * The token generator.
30 * @var \Drupal\Core\Access\CsrfTokenGenerator
35 * Constructs a new rest CSRF access check.
37 * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
38 * The session configuration.
39 * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
40 * The token generator.
42 public function __construct(SessionConfigurationInterface $session_configuration, CsrfTokenGenerator $csrf_token) {
43 $this->sessionConfiguration = $session_configuration;
44 $this->csrfToken = $csrf_token;
50 public function applies(Route $route) {
51 $requirements = $route->getRequirements();
52 // Check for current requirement _csrf_request_header_token and deprecated
54 $applicable_requirements = [
55 '_csrf_request_header_token',
56 // @todo Remove _access_rest_csrf in Drupal 9.0.0.
59 $requirement_keys = array_keys($requirements);
61 if (array_intersect($applicable_requirements, $requirement_keys)) {
62 if (isset($requirements['_method'])) {
63 // There could be more than one method requirement separated with '|'.
64 $methods = explode('|', $requirements['_method']);
65 // CSRF protection only applies to write operations, so we can filter
66 // out any routes that require reading methods only.
67 $write_methods = array_diff($methods, ['GET', 'HEAD', 'OPTIONS', 'TRACE']);
68 if (empty($write_methods)) {
72 // No method requirement given, so we run this access check to be on the
81 * @param \Symfony\Component\HttpFoundation\Request $request
83 * @param \Drupal\Core\Session\AccountInterface $account
84 * The currently logged in account.
86 * @return \Drupal\Core\Access\AccessResultInterface
89 public function access(Request $request, AccountInterface $account) {
90 $method = $request->getMethod();
92 // This check only applies if
93 // 1. this is a write operation
94 // 2. the user was successfully authenticated and
95 // 3. the request comes with a session cookie.
96 if (!in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'])
97 && $account->isAuthenticated()
98 && $this->sessionConfiguration->hasSession($request)
100 if (!$request->headers->has('X-CSRF-Token')) {
101 return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
103 $csrf_token = $request->headers->get('X-CSRF-Token');
104 // @todo Remove validate call using 'rest' in 8.3.
105 // Kept here for sessions active during update.
106 if (!$this->csrfToken->validate($csrf_token, self::TOKEN_KEY)
107 && !$this->csrfToken->validate($csrf_token, 'rest')) {
108 return AccessResult::forbidden()->setReason('X-CSRF-Token request header is invalid')->setCacheMaxAge(0);
111 // Let other access checkers decide if the request is legit.
112 return AccessResult::allowed()->setCacheMaxAge(0);