Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2021-09-13T22:38:42.363Z
Answers.Text: This is the Craft 3 answer. Thanks, Ben!
Answers.UserDisplayName: Alex Kendrick
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2018-02-19T10:57:45.660Z
Answers.LastActivityDate: 2018-10-24T12:25:09.250Z
Answers.LastEditDate: 2018-10-24T12:25:09.250Z
Answers.UserDisplayName: Ben Croker
Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-01-26T18:22:28.743Z
Answers.LastActivityDate: 2017-01-26T18:22:28.743Z
Answers.LastEditDate: 2017-04-13T12:51:00.303Z
Answers.UserDisplayName: Michael Rog
Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2017-05-02T09:01:50.617Z
Answers.LastActivityDate: 2019-05-02T11:30:26.027Z
Answers.LastEditDate: 2019-05-02T11:30:26.027Z
Answers.UserDisplayName: Fred Carlsen
Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-12-30T10:48:51.073Z
Answers.Text: 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.
Answers.UserDisplayName: James White
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2021-05-25T08:26:56.717Z
Answers.LastActivityDate: 2021-05-25T08:26:56.717Z
Answers.UserDisplayName: Mostafa Soufi
Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2014-11-04T21:29:52.320Z
Answers.Text: Thanks Brad (and Brandon)! That'll work just fine for this particular thingamajig.
Answers.UserDisplayName: Matt Stein
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2014-11-20T14:34:18.703Z
Answers.Text: @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?
Answers.UserDisplayName: Mike Pierce
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2014-11-20T15:40:55.127Z
Answers.Text: Can you post this as a separate new question?
Answers.UserDisplayName: Brad Bell
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2014-11-21T10:48:38.110Z
Answers.Text: @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
Answers.UserDisplayName: Mike Pierce
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2014-11-21T17:31:58.630Z
Answers.Text: @MikePierce Actually it’s just a matter of checking `$_REQUEST['PATH_INFO']`, not `$_GET['p']`. Just updated the example code.
Answers.UserDisplayName: Brandon Kelly
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2015-12-16T23:44:12.153Z
Answers.Text: 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?
Answers.UserDisplayName: taylor
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2015-12-17T00:17:32.357Z
Answers.Text: @taylor Craft's `general.php` config file. Update the answer.
Answers.UserDisplayName: Brad Bell
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-03-22T15:57:05.937Z
Answers.Text: 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
Answers.UserDisplayName: billythekid
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2014-11-04T20:21:48.450Z
Answers.LastActivityDate: 2016-12-02T17:47:08.900Z
Answers.LastEditDate: 2016-12-02T17:47:08.900Z
Answers.UserDisplayName: Brad Bell
Body: <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>
  
Tags: <plugin-development><csrf>
Title: What's the best way to skip CSRF for plugin controllers?
ThreadText: # 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](https://github.com/yiisoft/yii/blob/1.1.15/framework/web/CHttpRequest.php#L135) 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](http://buildwithcraft.com/docs/config-settings#enableCsrfProtection) config setting for that request.
  
  ```php
  return array(
      'enableCsrfProtection' => (!isset($_SERVER['REQUEST_URI']) || $_SERVER['REQUEST_URI'] != '/actions/plugin/controller/action'),
  );
  
  ```
  
  > **Comments:**
  > **username_0**: Thanks username_7 (and username_9)! That'll work just fine for this particular thingamajig.  
  > **username_8**: @username_7 @username_9 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**: @username_7 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**: @username_8 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 [username_7's answer](https://craftcms.stackexchange.com/a/4554/689) suggests, you can conditionally [enable or disable CSRF production](http://buildwithcraft.com/docs/config-settings#enableCsrfProtection) 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...)*
  
  ```
  <?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;
      }
  
  }
  
  ```
  
  *(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 username_3'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.  
  
  ---
  
  