Disable CSRF token validation for certain paths

In my application, I needed to disable CSRF token validation for certain actions. This is a hack I wrote to accomplish said task.

As far as I can tell, CHttpRequest "happens" before CUrlManager, so route information is not available in a proper fashion. Because this application uses certain CUrlManager settings that make URLs look like "controller/action", this works. There probably are nicer ways of doing this, but this might be useful to someone.

One such way would be instantiating a “temporary” CUrlManager for finding out the proper route, but that’d be hack-ish as well.




The component:


<?php


class HttpRequest extends CHttpRequest

{

  public $noCsrfValidationRoutes = array();


  protected function normalizeRequest()

  {

    parent::normalizeRequest();

    $route = implode('/', array_slice(explode('/', Yii::app()->getUrlManager()->parseUrl($this)), 0, 2));


    if($this->enableCsrfValidation && array_search($route, $this->noCsrfValidationRoutes) !== false)

      Yii::app()->detachEventHandler('onbeginRequest',array($this,'validateCsrfToken'));

  }

}


?>


The config snippet


...

                          'request'=>array(

                                           'class'=>'HttpRequest',

                                           'enableCsrfValidation'=>true,

                                           'noCsrfValidationRoutes'=>array('some/route', 'some/otherroute'),

                                           'enableCookieValidation'=>true,

                                           ),


...



It’s better to do check in validateCsrfToken function only on POST requests, not at all requests. Like this:




public function validateCsrfToken($event)

{

        // only validate POST requests

        if ($this->getIsPostRequest())

        {

            $route = Yii::app()->urlManager->parseUrl(Yii::app()->getRequest());

            if(in_array($route, $this->dontCheckCsrf)) return true;

...




I’ve added a check for , for those multiple paths, ie "myPath/", instead of “myPath/1”, “myPath/2”, etc.


class HttpRequest extends CHttpRequest {

    public $noCsrfValidationRoutes = array();


    protected function normalizeRequest() {


        parent::normalizeRequest();

        if ($this->getIsPostRequest()) {

            if($this->enableCsrfValidation &&  $this->checkPaths() !== false)

                Yii::app()->detachEventHandler('onbeginRequest',array($this,'validateCsrfToken'));

        }

    }


    private function checkPaths() {


        foreach ($this->noCsrfValidationRoutes as $checkPath) {

            // allows * in check path

            if(strstr($checkPath, "*")) {

                $pos = strpos($checkPath, "*");

                $checkPath = substr($checkPath, 0, $pos);

                if(strstr($this->pathInfo, $checkPath)) {

                    return true;

                }

            } else {

                if($this->pathInfo == $checkPath) {

                    return true;

                }

            }

        }

        return false;

    }

}

works like a charm! Exactly what I was looking for :)

Hello,

I tried with this solutions, but it doesn’t work for me. I always get CSRF token could not be verified http 400 error.

my main.php configuration file contains following lines:

// Cross-site Request Forgery Prevention

‘request’=>array(

'class'=&gt;'HttpRequest',


'enableCsrfValidation'=&gt;true,


'enableCookieValidation'=&gt;true,


'noCsrfValidationRoutes'=&gt;array('shoppingkart/ccpaymentresponse',),

),

The ccpaymentresponse action is called by external application, so no CSRF token could be sent.

Bye!

Mauro

Hi Mauro,

Which code do you use in HttpRequest?

The one posted above by dead_eye. Class saved in /protected/components folder.

The class is called correctly (tested with some echo placed here and there).

Sorry for the late response :)

I looked through the code above and modified/optimised it a bit, so you can try it out:





<?php


class HttpRequest extends CHttpRequest {


    public $noCsrfValidationRoutes = array();


	/**

 	* Normalizes the request data.

 	* This method strips off slashes in request data if get_magic_quotes_gpc() returns true.

 	* It also performs CSRF validation if {@link enableCsrfValidation} is true.

 	*/

	protected function normalizeRequest()

	{

            parent::normalizeRequest();

            if ($this->getIsPostRequest() && $this->enableCsrfValidation && !$this->checkCurrentRoute())

                    Yii::app()->detachEventHandler('onbeginRequest', array($this, 'validateCsrfToken'));

        }

        

        /**

 		* Checks if current route should be validated by validateCsrfToken()

 		* @return boolean true if current route should be validated 

 		*/

        private function checkCurrentRoute() {

            foreach ($this->noCsrfValidationRoutes as $checkPath) 

            {

                if (($pos = strpos($checkPath, "*")) !== false) 

                {

                    $checkPath = substr($checkPath, 0, $pos - 1);

                    if (strpos($this->pathInfo, $checkPath) == 0)

                        return false;

                } elseif ($this->pathInfo === $checkPath)

                    return false;

            }

            return true;

        }

}



Also, as an option, you may overwrite validateCsrfToken($event) instead of normalizeRequest() and put $this->checkCurrentRoute() to the first check, e.g.




	public function validateCsrfToken($event)


	{

		if($this->getIsPostRequest() && $this->checkCurrentRoute())

		{

         			parental code...

                }

        }

Not sure at the moment what would be faster and if there’s any significant difference at all.

Hope it’ll help and I didn’t miss anything :),

Yuga

No way, I still get CSRF token error :(

If I disable CSRF token validation application-wise, POST is ok. I think that the credt card payment gateway that should send me a POST request does someting strange I cannot understand. :unsure:

Trivial, but first of all check at which exact path do you get the payment notification and if this is the path pointed in config file.

Then you may Yii::log() which way the request is processed inside HttpRequest to find the problem, or make custom request to the url for debugging.

Ah, problem solved.

The noCsrfValidationRoutes configuration array should contain the URL in final form and not in the form of route/action form, so if you have URL Manager components configured to translate request you have to put in the HttpRequest component configuration array in the final form.

In my case I needed simply to change these lines of code:




// Cross-site Request Forgery Prevention

            'request'=>array(

                'class'=>'application.components.HttpRequest',

                'enableCsrfValidation'=>true,

                'enableCookieValidation'=>true,

                'noCsrfValidationRoutes'=>array('shoppingkart/ccpaymentresponse'),

            ),



into:




// Cross-site Request Forgery Prevention

            'request'=>array(

                'class'=>'application.components.HttpRequest',

                'enableCsrfValidation'=>true,

                'enableCookieValidation'=>true,

                'noCsrfValidationRoutes'=>array('shoppingkart/ccpaymentresponse.html'),

            ),



This is not a good solution. It would be better to use a filter for CSRF that works before URL manager. I will try to make it.

Thank you Yuga for you help!

Mauro

Welcome.

Nice to hear you solved the issue :)

@ToolMayNARD:thank you very much.your solution worked for me.Saved my time.thanks alot.

Thank you yugene, just a tiny correction. I had to change


if (strpos($this->pathInfo, $checkPath) == 0)

into


if (strpos($this->pathInfo, $checkPath) === 0)

Cheers