When logging in via services with authcache enabled something goes wrong with the session id (or possibly something else) resulting in a CSRF token problem.

Comments

znerol’s picture

Thanks for the report. Is this with Services module? If so please provide some more detail on your setup and also whether you are trying to cache authenticated responses delivered through services.

scotthooker’s picture

Hi Znerol,

Its quite complicated to explain and to debug.

However, this is my setup

Drupal Services module is being used.

Service call to login via a remote app the call goes to index.php?q=service_endpoint/user/login.json

If I then logout and login the service resource will return an error due to an invalid CSRF token -

Inside this function is where the error is generated within services

function _services_sessions_authenticate_call($module, $controller) {
  global $user;
  $original_user = services_get_server_info('original_user');
  if ($original_user->uid == 0) {
    return;
  }

  if ($controller['callback'] != '_user_resource_get_token') {
    $non_safe_method_called = !in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD', 'OPTIONS', 'TRACE'));
    $csrf_token_invalid = !isset($_SERVER['HTTP_X_CSRF_TOKEN']) || !drupal_valid_token($_SERVER['HTTP_X_CSRF_TOKEN'], 'services');
    if ($non_safe_method_called && $csrf_token_invalid) {
      return t('CSRF validation failed');
    }
  }

  if ($user->uid != $original_user->uid) {
    $user = $original_user;
  }
}

The actual error is when checking drupal_valid_token($_SERVER['HTTP_X_CSRF_TOKEN'], 'services')

Works fine with authcache off. Broken with authcache on. And yes authcache is caching for all users. There seems to be no way to exclude this caching for this service callback.

znerol’s picture

Navigate to Administration » Configuration » System » Authcache Page Caching » Settings and add service_endpoint to the list of excluded pages. This should do the trick.

scotthooker’s picture

Tried that. Tried various combinations of that too

service_endpoint*
service_endpoint/*

The full end point path...

Had me thinking it could be the fact the service endpoints are called with non-clean urls. But looked through the authcache code and that should be handled ok.

scotthooker’s picture

My hunch is that something is weird within

function drupal_get_token($value = '') {
  return drupal_hmac_base64($value, session_id() . drupal_get_private_key() . drupal_get_hash_salt());
}

or

function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
  global $user;
  watchdog($token, drupal_get_token($value));
  return (($skip_anonymous && $user->uid == 0) || ($token === drupal_get_token($value)));
}
znerol’s picture

Can you break down your setup into a minimal sequence of steps I can follow in order to reproduce this issue (e.g. install drupal, enable authcache, enable services, set-up endpoint X, use wget/curl with parameters Y)? Alternatively, do you know of a tutorial/documentation page on how to set up services such that it roughly covers your use case?

If you are comfortable with Features module, perhaps you can setup and export a minimum configuration as a feature?

znerol’s picture

drupal_get_token() and drupal_valid_token() are integral parts of the core security infrastructure. The main use is to protect against CSRF attacks. Although I do not yet understand why and how services is using them.

scotthooker’s picture

Hi Znerol,

Its quite specific I guess so you might not want to spend too much time looking into it. I just wanted to ask quickly to see if there was a quick solution.

/**
 * Authenticates a call using Drupal's built in sessions
 *
 * @return string
 *   Error message in case error occured.
 */
function _services_sessions_authenticate_call($module, $controller) {
  global $user;
  $original_user = services_get_server_info('original_user');
  if ($original_user->uid == 0) {
    return;
  }

  if ($controller['callback'] != '_user_resource_get_token') {
    $non_safe_method_called = !in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD', 'OPTIONS', 'TRACE'));
    $csrf_token_invalid = !isset($_SERVER['HTTP_X_CSRF_TOKEN']) || !drupal_valid_token($_SERVER['HTTP_X_CSRF_TOKEN'], 'services');
    if ($non_safe_method_called && $csrf_token_invalid) {
      return t('CSRF validation failed');
    }
  }

  if ($user->uid != $original_user->uid) {
    $user = $original_user;
  }
}

Is basically what services is calling. It throws an error after I've logged in, logged out and am logging back in again as if its caching the wrong token. Or something is screwed with the sessions.

znerol’s picture

I played around with services a little bit and what I found is the following:

  • XMLRPC is using POST requests exclusively. Authcache will never cache POST requests, so I guess we do not have a problem here.
  • When using endpoints with REST, you have GET requests for some actions (like index). However, in the standard configuration authcache will not cache responses if their mime-type is not in the list of allowed ones. At the moment only 5 are allowed by default:
    text/html
    text/javascript
    text/plain
    application/xml
    application/atom+xml
    

    So if you are using JSON or something, authcache should not kick in at all.

Therefore I'm a bit out of ideas on what could go wrong here.

Which storage backend do you use? I.e., do you have Varnish running?

znerol’s picture

Status: Active » Postponed (maintainer needs more info)
scotthooker’s picture

Status: Postponed (maintainer needs more info) » Closed (works as designed)

After much debugging this was a services related issue.

tflanagan’s picture

@scotthooker What was your fix for this issue?