<p>In Craft 3 this is a cinch:</p>

<pre><code>class PluginController extends Controller
{
    // Disable CSRF validation for the entire controller 
    public $enableCsrfValidation = false;

    ...
</code></pre>

<p>See the Yii2 docs for more control over inividual actions:
<a href="https://github.com/yiisoft/yii2/blob/master/docs/guide/security-best-practices.md#avoiding-csrf" rel="noreferrer">https://github.com/yiisoft/yii2/blob/master/docs/guide/security-best-practices.md#avoiding-csrf</a></p>

CC BY-SA 4.0
2021-09-13T22:38:42.363Z
This is the Craft 3 answer. Thanks, Ben!
Alex Kendrick
CC BY-SA 4.0
2018-02-19T10:57:45.660Z
2018-10-24T12:25:09.250Z
2018-10-24T12:25:09.250Z
Ben Croker
<p>As <a href="https://craftcms.stackexchange.com/a/4554/689">Brad's answer</a> suggests, you can conditionally <a href="http://buildwithcraft.com/docs/config-settings#enableCsrfProtection" rel="noreferrer">enable or disable CSRF production</a> via the General Config, based on any criteria you want.</p>

<p>Some use cases might be:</p>

<ul>
<li><p>match a <code>SERVER['REQUEST_URI']</code> string or substring, to open up a particular set of controllers or actions</p></li>
<li><p>check the <code>$_SERVER['REMOTE_ADDR']</code>, to allow token-less requests from a particular IP or IP range</p></li>
<li><p>validate 'safe' requests based on a key or encrypted timestamp from some <code>$_POST[]</code> or <code>$_GET[]</code> variable</p></li>
</ul>

<p>For cleanliness, I typically try to tuck the checking/validation logic away into its own "handler" class. Then, in the <code>general.php</code>, I set the <code>enableCsrfProduction</code> item based on the result of the check...</p>

<p><em>(In MyRequestHelper.php, something like...)</em></p>

<pre><code>&lt;?php
namespace Craft;

class MyRequestHelper
{

    public static function isApiRequest()
    {
        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        return strpos($path, 'actions/my-api') == 1;
    }

}
</code></pre>

<p><em>(In craft/config/general.php, something like...)</em></p>

<pre><code>require_once (..path/to/MyRequestHelper.php');
$isApiRequest = \Craft\MyRequestHelper::isApiRequest();

return [

    // ...other configs...

    'enableCsrfProtection' =&gt; !$isApiRequest,

];
</code></pre>

CC BY-SA 3.0
2017-01-26T18:22:28.743Z
2017-01-26T18:22:28.743Z
2017-04-13T12:51:00.303Z
Michael Rog
<p><strong>UPDATE: This is not necessary to do when using Commerce 2.</strong></p>

<hr>

<p>I modified Michael's answer to add support for query strings + make it nicer to check for multiple routes:</p>

<pre><code>if ( !function_exists('isApiRequest') ) {
    function isApiRequest ()
    {
        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);

        $actions = [
            'actions/charge/webhook/callback',
            'actions/commerce/payments/completePayment',
            'actions/some/other/action',
            'actions/commerce/webhooks/process-webhook'

    ];

    foreach ($actions as $action) {
            if ( strpos($path, $action) || strpos($query, $action) ) {
                return true;
            }
        }

    return false;
    }
}

return [

    // ...other configs...

    'enableCsrfProtection' =&gt; !isApiRequest(),

];
</code></pre>

CC BY-SA 4.0
2017-05-02T09:01:50.617Z
2019-05-02T11:30:26.027Z
2019-05-02T11:30:26.027Z
Fred Carlsen
<p>Try to disable the CSRF protection by event</p>
<pre><code>use Yii;
use yii\base\ActionEvent;
use yii\base\Event;
use yii\web\Controller;

Event::on(Controller::class, Controller::EVENT_BEFORE_ACTION, function (ActionEvent $actionEvent) {
    if ($actionEvent-&gt;action-&gt;id == 'update-cart') {
        Yii::$app-&gt;controller-&gt;enableCsrfValidation = false;
    }
});
</code></pre>

CC BY-SA 4.0
2024-12-30T10:48:51.073Z
If you want to be absolutely sure you target the controller and action required, you can also check the namespace of the controller first to ensure it is the one. e.g. `$actionEvent->action->controller instanceof ExampleController `. This prevents running this on two different controllers which might share the same action ID value.
James White
CC BY-SA 4.0
2021-05-25T08:26:56.717Z
2021-05-25T08:26:56.717Z
Mostafa Soufi
<p>Craft is piggybacking off of Yii's CSRF support for 90% of the work here and they've implemented it at the <a href="https://github.com/yiisoft/yii/blob/1.1.15/framework/web/CHttpRequest.php#L135" rel="noreferrer">Request</a> level (specifically <code>onBeginRequest</code>).</p>

<p>That happens way before Craft gets a chance to determine what type of request this is, much less figure out where to route it.</p>

<p>I think your best bet is going to be to put some logic in Craft's <code>general.php</code> config file that specifically check's for PHP's <code>$_GET</code> and <code>$_POST</code> globals for a <code>p=some/path/to/my/plugin/controller</code> and disables the <a href="http://buildwithcraft.com/docs/config-settings#enableCsrfProtection" rel="noreferrer">enableCsrfProtection</a> config setting for that request.</p>

<pre class="lang-php prettyprint-override"><code>return array(
    'enableCsrfProtection' =&gt; (!isset($_SERVER['REQUEST_URI']) || $_SERVER['REQUEST_URI'] != '/actions/plugin/controller/action'),
);
</code></pre>

CC BY-SA 3.0
2014-11-04T21:29:52.320Z
Thanks Brad (and Brandon)! That'll work just fine for this particular thingamajig.
Matt Stein
CC BY-SA 3.0
2014-11-20T14:34:18.703Z
@BradBell @BrandonKelly I'm trying to do something similar, I have an action that processes Paypal IPN requests, but using `{{ actionUrl('plugin/action', {}) }}` in my template forces `$mustShowScriptName` to be set to true in `UrlHelper::_getUrl()`. This means the global `$_GET['p']` is not set, as the URL that is printed includes "index.php" like this `http://craft.dev/index.php/actions/plugin/action?param=foo`. `omitScriptNameInUrls` set to true in config seems to have no effect on this. Can you think of what's going wrong?
Mike Pierce
CC BY-SA 3.0
2014-11-20T15:40:55.127Z
Can you post this as a separate new question?
Brad Bell
CC BY-SA 3.0
2014-11-21T10:48:38.110Z
@BradBell Of course, sorry about that: http://craftcms.stackexchange.com/questions/4858/how-can-i-ensure-actionurl-from-template-does-not-show-script-name-in-the-genera
Mike Pierce
CC BY-SA 3.0
2014-11-21T17:31:58.630Z
@MikePierce Actually it’s just a matter of checking `$_REQUEST['PATH_INFO']`, not `$_GET['p']`. Just updated the example code.
Brandon Kelly
CC BY-SA 3.0
2015-12-16T23:44:12.153Z
When you say "`config file`", do you mean put that in a plugin's config file, or do you mean in the `general.php` configuration file for Craft?
taylor
CC BY-SA 3.0
2015-12-17T00:17:32.357Z
@taylor Craft's `general.php` config file. Update the answer.
Brad Bell
CC BY-SA 3.0
2017-03-22T15:57:05.937Z
If you're coming here from https://craftcommerce.com/docs/payment-gateways the action routes for the payment gateway callbacks that you want to disable csrf for, according to Luke, are `actions/commerce/payments/completePayment` for all,  or `actions/commerce/payments/acceptNotification` if you use sagepay
billythekid
CC BY-SA 3.0
2014-11-04T20:21:48.450Z
2016-12-02T17:47:08.900Z
2016-12-02T17:47:08.900Z
Brad Bell
<p>I'm working on a plugin that'll create a private API for content authors to post entries to Craft from desktop-based scripts. These scripts post data to plugin controller actions (anonymous-allowed), where the plugin will first check for valid Craft user account details or a plugin-generated key.</p>

<p><strong>Since I'm still enforcing authentication, what's the best way to disable CSRF checks <em>just for these posts</em> when CSRF is globally enabled?</strong></p>

<p>Thoughts I've had:</p>

<ol>
<li>Attempt to put the CSRF key and token in the $_POST data as early as possible. (Probably not even possible.)</li>
<li>Have the client (desktop) script grab the CSRF key+token just before posting. (Inconvenient, potentially failure-prone.)</li>
<li>Somehow prevent CSRF protection from being enabled for requests passed to this controller.</li>
</ol>

<plugin-development><csrf>
What's the best way to skip CSRF for plugin controllers?
# What's the best way to skip CSRF for plugin controllers?

(Asked by: username_0 on 2014-11-04)

I'm working on a plugin that'll create a private API for content authors to post entries to Craft from desktop-based scripts. These scripts post data to plugin controller actions (anonymous-allowed), where the plugin will first check for valid Craft user account details or a plugin-generated key.

**Since I'm still enforcing authentication, what's the best way to disable CSRF checks *just for these posts* when CSRF is globally enabled?**

Thoughts I've had:

1. Attempt to put the CSRF key and token in the $\_POST data as early as possible. (Probably not even possible.)
2. Have the client (desktop) script grab the CSRF key+token just before posting. (Inconvenient, potentially failure-prone.)
3. Somehow prevent CSRF protection from being enabled for requests passed to this controller.

**Tags:** plugin-development, csrf


---

## 5 answers

### Answer by username_7 on 2014-11-04. Score: 13 | Accepted answer

Craft is piggybacking off of Yii's CSRF support for 90% of the work here and they've implemented it at the Request level (specifically onBeginRequest).
That happens way before Craft gets a chance to determine what type of request this is, much less figure out where to route it.
I think your best bet is going to be to put some logic in Craft's general.php config file that specifically check's for PHP's $_GET and $_POST globals for a p=some/path/to/my/plugin/controller and disables the enableCsrfProtection config setting for that request.
return array(
    'enableCsrfProtection' => (!isset($_SERVER['REQUEST_URI']) || $_SERVER['REQUEST_URI'] != '/actions/plugin/controller/action'),
);

> **Comments:**
> **username_0**: Thanks Brad (and Brandon)! That'll work just fine for this particular thingamajig.  
> **username_8**: @BradBell @BrandonKelly I'm trying to do something similar, I have an action that processes Paypal IPN requests, but using `{{ actionUrl('plugin/action', {}) }}` in my template forces `$mustShowScriptName` to be set to true in `UrlHelper::_getUrl()`. This means the global `$_GET['p']` is not set, as the URL that is printed includes "index.php" like this `http://craft.dev/index.php/actions/plugin/action?param=foo`. `omitScriptNameInUrls` set to true in config seems to have no effect on this. Can you think of what's going wrong?  
> **username_7**: Can you post this as a separate new question?  
> **username_8**: @BradBell Of course, sorry about that: http://craftcms.stackexchange.com/questions/4858/how-can-i-ensure-actionurl-from-template-does-not-show-script-name-in-the-genera  
> **username_9**: @MikePierce Actually it’s just a matter of checking `$_REQUEST['PATH_INFO']`, not `$_GET['p']`. Just updated the example code.  
> **username_10**: When you say "`config file`", do you mean put that in a plugin's config file, or do you mean in the `general.php` configuration file for Craft?  
> **username_7**: @username_10 Craft's `general.php` config file. Update the answer.  
> **username_11**: If you're coming here from https://craftcommerce.com/docs/payment-gateways the action routes for the payment gateway callbacks that you want to disable csrf for, according to Luke, are `actions/commerce/payments/completePayment` for all,  or `actions/commerce/payments/acceptNotification` if you use sagepay  

---

### Answer by username_1 on 2018-02-19. Score: 23

In Craft 3 this is a cinch:
class PluginController extends Controller
{
    // Disable CSRF validation for the entire controller 
    public $enableCsrfValidation = false;

    ...

See the Yii2 docs for more control over inividual actions:
https://github.com/yiisoft/yii2/blob/master/docs/guide/security-best-practices.md#avoiding-csrf

> **Comments:**
> **username_2**: This is the Craft 3 answer. Thanks, Ben!  

---

### Answer by username_3 on 2017-01-26. Score: 11

As Brad's answer suggests, you can conditionally enable or disable CSRF production via the General Config, based on any criteria you want.
Some use cases might be:

match a SERVER['REQUEST_URI'] string or substring, to open up a particular set of controllers or actions
check the $_SERVER['REMOTE_ADDR'], to allow token-less requests from a particular IP or IP range
validate 'safe' requests based on a key or encrypted timestamp from some $_POST[] or $_GET[] variable

For cleanliness, I typically try to tuck the checking/validation logic away into its own "handler" class. Then, in the general.php, I set the enableCsrfProduction item based on the result of the check...
(In MyRequestHelper.php, something like...)

(In craft/config/general.php, something like...)
require_once (..path/to/MyRequestHelper.php');
$isApiRequest = \Craft\MyRequestHelper::isApiRequest();

return [

    // ...other configs...

    'enableCsrfProtection' => !$isApiRequest,

];


---

### Answer by username_4 on 2017-05-02. Score: 10

UPDATE: This is not necessary to do when using Commerce 2.

I modified Michael's answer to add support for query strings + make it nicer to check for multiple routes:
if ( !function_exists('isApiRequest') ) {
    function isApiRequest ()
    {
        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);

        $actions = [
            'actions/charge/webhook/callback',
            'actions/commerce/payments/completePayment',
            'actions/some/other/action',
            'actions/commerce/webhooks/process-webhook'

    ];

    foreach ($actions as $action) {
            if ( strpos($path, $action) || strpos($query, $action) ) {
                return true;
            }
        }

    return false;
    }
}

return [

    // ...other configs...

    'enableCsrfProtection' => !isApiRequest(),

];


---

### Answer by username_5 on 2021-05-25. Score: 4

Try to disable the CSRF protection by event
use Yii;
use yii\base\ActionEvent;
use yii\base\Event;
use yii\web\Controller;

Event::on(Controller::class, Controller::EVENT_BEFORE_ACTION, function (ActionEvent $actionEvent) {
    if ($actionEvent->action->id == 'update-cart') {
        Yii::$app->controller->enableCsrfValidation = false;
    }
});

> **Comments:**
> **username_6**: If you want to be absolutely sure you target the controller and action required, you can also check the namespace of the controller first to ensure it is the one. e.g. `$actionEvent->action->controller instanceof ExampleController `. This prevents running this on two different controllers which might share the same action ID value.  

---

