Using CORS filter to block Ajax Requests

梦想的初衷 提交于 2019-12-11 06:36:01

问题


I am trying to understand how CORS Filter works and how can one benefit from it especially when we are talking about the Origin option.

I am working on a project where i need to provide access for the client to add lead into the API using Ajax requests.

I am using yii\rest\Controller for my controller. I have an API already in place and working, I have a base controller where I set my behavior and other common methods and extend all my controllers from this base controller.

The code for the base controller is below

class ApiController extends yii\rest\Controller
{
    /**
     * @var array
     */
    protected $_logs = [];

    /**
     * Implemented CORS and HTTP Basic Auth for API
     *
     * @return string
     */
    public function behaviors()
    {
        $behaviors = parent::behaviors();
        // remove authentication filter
        $auth = $behaviors['authenticator'];

        // remove authentication filter necessary because we need to
        // add CORS filter and it should be added after the CORS
        unset($behaviors['authenticator']);

        // add CORS filter
        $behaviors['corsFilter'] = [
            'class' => Cors::class,
            'cors' => [
                'Origin' => ['http://www.my-sms-business.local', 'http://localhost'],
                'Access-Control-Request-Method' => ['GET', 'POST'],
            ],
        ];

        // re-add authentication filter
        $behaviors['authenticator'] = $auth;
        // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
        $behaviors['authenticator']['except'] = ['options'];

        return $behaviors;
    }
}

As you can see I have the CORS defined with http://localhost added into the Origin, now as per Yii it should allow the requests from these domains and it should work smoothly, Yes it does work correctly when I use a simple Html page with a form and javascript code to submit the lead to the API, the code looks like below.

$(document).ready(function() {
  $("#my-form").submit(function(e) {
    e.preventDefault();
    //serialize form input data
    let data = $(this).serialize();
    //get the form action
    let url = $(this).attr('action');

    $.ajax({
      url: url,
      method: 'POST',
      data: data,
      success: function(data) {
        if (data.status == 200) {
          alert(data.message);
        } else {
          alert("An error occoured see details,\n " + data.message.join(
            "\n"));
        }
      },
    })
    return false;
  });
});
<form name="my-form" id="my-form" method="POST" action="http://www.my-sms-business.local/api/v1/lead/add?access-token=MY-ACCESS-TOKEN">
  <label>Phone</label>
  <input type="text" name="phone_number" id="phone_number" />
  <label>Campaign Id</label>
  <input type="text" name="campaign_id" />
  <label>user ID</label>
  <input type="text" name="user_id" />
  <label>name</label>
  <input type="text" name="name" />

  <input type="submit" value="Submit" />
</form>

The LeadController uses QueryParamAuth as i send the access-token via query string in the url of the ajax call.

class LeadController extends ApiController
{

    /**
     * @return mixed
     */
    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => QueryParamAuth::class,
        ];

        return $behaviors;
    }

    /**
     * Add the subscriber to the campaign
     *
     * @return mixed
     */
    public function actionAdd()
    {
        if (Yii::$app->request->isPost) {
            //start db transaction
            $transaction = Yii::$app->db->beginTransaction();

            //post data
            $post['SubscribersForm'] = Yii::$app->request->post();
            $model = new SubscribersForm();
            $model->active = SubscribersForm::ACTIVE_YES;
            $model->type = SubscribersForm::TYPE_SINGLE;

            try {
                if ($model->load($post)) {
                    if ($model->validate()) {
                        //if subscriber already exists in the database
                        $subscriberExists = Subscriber::findOne(['phone_number' => $model->phone_number]) !== null;

                        // call add subscriber so that the subscriber is added to the
                        // specified campaign and also to other campaigns under the
                        // same number the current campaign is associated to.
                        $model->saveSubscribers();

                        //commit the transaction
                        $transaction->commit();

                        if ($subscriberExists) {
                            return $this->formatMessage(['The Phone exsts in our database, we have added the number to the campaign that was provided.'], 200);
                        } else {
                            return $this->formatMessage(['Lead added successfully'], 200);
                        }

                    } else {
                        return $this->formatMessage($model->getErrorSummary(true), 401);
                    }
                }
            } catch (Exception $ex) {

                //roll back the transaction
                $transaction->rollBack();

                //return the error response
                return $this->formatMessage([$ex->getMessage()], $ex->getCode());
            }
        }

    }
}

Now I wanted to test that if I remove http://localhost from the Origin it should not allow the request from that origin. When I remove it and hit the submit button on the form to send the ajax call I see that the console is throwing the warning

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://www.my-sms-business.local/api/v1/lead/add?access-token=MY-ACCESS-TOKEN. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

And the alerts that are inside the success callback of the ajax call does not trigger, so far so good.

BUT when I click on the request that was generated and look into the details BOOM, it still goes ahead to the actionAdd() in the LeadController and attempts to add the lead? See the image below

Is it how it is going to work or works? I don't think so, because i thought if the allowed domain is not present in the Origin the request would terminate. Currently, the way it is behaving it becomes a useless option in this way.

SO where am I doing it wrong here? What config am i missing. We are planning to provide an option on the frontend of the site where users can generate their API key and add their domains to whitelist from where they will be sending the requeststo add leads.

UPDATE

To my surprise if i use the HttpBasicAuth and add headrs via my ajax call using beforeSendlike below

beforeSend: function (xhr) {
    xhr.setRequestHeader("Authorization",
        "Basic " + btoa('MY_ACCESS_TOKEN:'));
    xhr.setRequestHeader("Content-Type",
        "application/x-www-form-urlencoded");
},

and remove the QueryParamAuth from my LeadController. And update the behaviors() method in the ApiController to the following

public function behaviors()
{
    $behaviors = parent::behaviors();
    // remove authentication filter
    $auth = $behaviors['authenticator'];

    // remove authentication filter necessary because we need to
    // add CORS filter and it should be added after the CORS
    unset($behaviors['authenticator']);

    // add CORS filter
    $behaviors['corsFilter'] = [
        'class' => Cors::class,
        'cors' => [
            'Origin' => ['http://www.my-sms-business.local', 'http://localhost'],
            'Access-Control-Request-Method' => ['GET', 'POST', 'OPTIONS'],
            'Access-Control-Request-Headers' => ['Authorization'],
            'Access-Control-Allow-Credentials' => true,

        ],
    ];

    // re-add authentication filter
    $behaviors['authenticator'] = [
        'class' => HttpBasicAuth::class,
    ];
    // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
    $behaviors['authenticator']['except'] = ['OPTIONS'];

    return $behaviors;
}

It starts working as expected if i remove a domain from my whitelist or Origin it does not add the lead, as the OPTIONS request is sent before the actual request is sent and when it fails it does not initiate the POST request.

So What is all this about, why it moves on to the action even if the domain is not in the whitelist when using the QueryParamAuth?.

来源:https://stackoverflow.com/questions/55865764/using-cors-filter-to-block-ajax-requests

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!