1

I've got a controller action which will work as the callback URL for OAUTH authentication with an external, third-party API. After authenticating, the external API will issue a GET request to my callback URL, which means I need to allow anonymous access for the action method. However, I'd like to use CSRF protection for it.

I've set enableCsrfProtection to true in my config/general.php file, and the tokenParam setting is token. I've also added protected $allowAnonymous = array('actionAuthCallback'); to my Controller.

Here's how I create the callback URL:

$callbackUrl = UrlHelper::getUrlWithToken(UrlHelper::getSiteUrl('actions/myplugin/mycontroller/authCallback'), craft()->request->getCsrfToken());

The resulting URL looks like this:

https://mysite.com/actions/myplugin/mycontroller/authCallback?token=2423552353djfsjfdsfjsdfsdfrandomrandomawesome

I've validated that the correct action method fires, so all good so far.

Here's the important bits for the action:

public function actionAuthCallback()
{
    $this->requireToken();
    ...
    $this->returnJson(array(
        'success' => true,
    ));
}

If I call the action URL without the token query parameter, stuff works as expected – Craft throws a 400, as it should.

However, if I do include the token parameter in the URL, Craft throws a 404. What's even more confusing, this happens even if I leave out the $this->requireToken(); call in the actionAuthCallBack() method – as soon as Craft sees the token query parameter in the URL, the 404 exception is thrown.

What am I doing wrong? Does CSRF protection require POST requests?

2

According to https://craftcms.com/support/csrf-protection CSRF does require POST requests

With CSRF protection enabled, all of your site’s visitors will get a “CRAFT_CSRF_TOKEN” cookie set on their browser, and all POST requests must be accompanied by a POST parameter with a matching name and value (the CSRF Token).

On top of that, you'll need to (manually) enable it in your config:

You will need to enable it to take advantage of it. You can do that by setting the enableCsrfProtection config setting in your craft/config/general.php file.

  • 1
    Thanks – I missed that part in the docs! Oh well, I'll try to find a workaround and post it as an additional answer, in case anyone else needs CSRF protection for GET requests. – Mats Mikkel Rummelhoff Feb 4 '16 at 9:28
1

Chris is quite correct – CSRF does not work with GET requests (see this answer by Brad Bell for an explanation). I'm posting my workaround as an additional answer in case anyone else ever needs to have CSRF validation for GET requests working.

To get around the issue, I set up a custom config key in /config/general.php called tokenGetParam. This is because you can't use the native tokenParam config setting for GET requests – Craft's CSRF validation will hijack the request and throw a 404 exception as soon as it sees the token in the URL, if it's a GET request. Obviously, the tokenGetParam setting needs to have a different value than tokenParam, as well – I went with "t". A custom config value isn't really necessary – you can just hard code along the way – but it's helpful to have if you ever need to change it in a pinch.

Of course, not being able to use the native tokenParam config value also means that you can't use the various built-in methods for dealing with CSRF tokens, e.g. $this->requireToken() for the Controller or UrlHelper::getUrlWithToken(), etc.

In my case, my needs were very simple. I simply needed to include a valid CSRF token in a GET variable tokenGetParam (i.e. "t") in the callback URL to the external API, so that whenever the external API calls that URL after authenticating stuff on their end, I'd be able to accept or reject the request based on the contents of the "t" query parameter.

Here's how I generate the callback URL before calling the external webservice:

$callbackUrl = UrlHelper::getSiteUrl('actions/myplugin/mycontroller/authCallback').'?'.craft()->config->get('tokenGetParam').'='.craft()->request->getCsrfToken();

When the actionAuthCallback is called, I validate the token like this:

$requestToken = craft()->request->getQuery(craft()->config->get('tokenGetParam'));
if ($requestToken !== craft()->request->getCsrfToken()) {
    // Token is not valid
    throw new HttpException(400);
}

Works.

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.