3

I am working on migrating a custom API from D7 to D8 and am doing so by using module routing. I have a routing.yml file that looks like so:

ytp_user.api.login:
  path: "/api/v1/user/login"
  defaults:
    _title: "User API Login"
    _controller: '\Drupal\ytp_user\Controller\UserAPIController::login'
  methods: [POST]
  requirements:
    _csrf_request_header_token: "TRUE"
ytp_user.api.get_current_user:
  path: "/api/v2/user/me"
  defaults:
    _title: "User API Get Current User"
    _controller: '\Drupal\ytp_user\Controller\UserAPIController::getCurrentUser'
  methods: [GET]
  requirements:
    _custom_access: '\Drupal\ytp_user\Controller\UserAPIController::authorize'

In the first route, I am trying to validate a csrf token provided in the headers, via the requirement _csrf_request_header_token: "TRUE", however, this does absolutely nothing. It doesn't validate or even check that the header exists as the documentation suggests it should.

I've tried replacing this with _csrf_token: 'TRUE' which at least checks that the token exists in the query, but I can't get a valid token!!! If I grab a token from /session/token and then pass it along in the query as ?token=<token-from-session/token> then I just get an error saying the CSRF token is invalid.

Am I missing another step or is the documentation just straight up incorrect? I have been searching for hours and everything suggests that what I have should work, so why is the CSRF token not being validated properly?

5
  • I've not used _csrf_request_header_token but I believe it's entirely separate from _csrf_token. The latter is the vanilla way of requiring a CSRF token to a route (eg. Devel uses it on the Clear cache route), and it's supplied as a query parameter: see drupal.org/node/3048359. IIUC the header token was extracted from the REST module, uses the token from the /session/token endpoint and is for remote API use. Are you issuing a post/patch/put? Have you authenticated?
    – Andy
    Jan 15 at 10:01
  • 1
    I read something recently that mentioned anon users don't have real sessions, that sessions are reinitialized for each request. To Andy's point, I wonder if you need to authenticate before these tokens can work.
    – sonfd
    Jan 15 at 14:16
  • Yeah, that’s exactly what the documentation says, but it’s not working as suggested. When using _csrf_token it checks for the token, but says it’s invalid when the token is from /session/token. When using _csrf_request_header_token, it doesn’t check the header at all. I’m issuing a post call, and I’m trying to authenticate, thats what I’m trying to get the csrf token for, to login a user. Is /session/token suppose to create cookie headers or something? I’m not sure what to connect, the documentation is absolute garbage.
    – BlondeSwan
    Jan 15 at 14:18
  • That doesn’t make sense, wouldn’t you want to verify CSRF before you authenticate a user?
    – BlondeSwan
    Jan 15 at 14:19
  • It doesn't look like it? drupal.stackexchange.com/a/221045/48114 (I really don't have any first hand experience with this though.)
    – sonfd
    Jan 15 at 14:27
1

The requirement _csrf_request_header_token: "TRUE" works only if user is connected because CsrfRequestHeaderAccessCheck use session informations.
If you look at CsrfRequestHeaderAccessCheck in the access method there is:

 if ($account
      ->isAuthenticated() && $this->sessionConfiguration
      ->hasSession($request)) {

To check if the user is connected and it there is a session identifier in the request if it's not it will go directly to:

// Let other access checkers decide if the request is legit.
    return AccessResult::allowed()->setCacheMaxAge(0);

so it will not check if X-CSRF-Token is in the headers or not even if you set the route requirement _csrf_request_header_token: "TRUE".
Code MODULE.routing.yml MODULE.login:

  path: "/api/v1/user/login"
  defaults:
    _title: "User API Login"
    _controller: '\Drupal\MODULE\Controller\UserAPIController::login'
  methods: 'POST'
  requirements:
    _csrf_request_header_token: 'TRUE'

UserAPIController.php

class UserAPIController extends ControllerBase {
  
  public function login(Request $request) {
    $response['data'] = 'Some test data to return';
    $response['method'] = 'POST';

    return new JsonResponse( $response );
  }

}

Test

  1. With Connected user enter image description here
  2. with anonymous user enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.