Yii Framework Forum

CORS preflight request bug with angular


(Dniznick) #1

I am learning angular and in the process found this tutorial series (part1, part2) which integrates Yii and Angular.

Unfortunately, I was coming up against some problems. I could request data from API unless auth was required. I was getting errors relating to preflight checks when trying to submit auth and it turns out Yii’s CORS filter was not letting the request through.

After much trial and error and reading issues/pull requests on github I decided to extend the yii2 core Cors.php class based on this PR. File attached.

I use it like this:


<?php

namespace frontend\controllers;


use Yii;

use common\models\LoginForm;

use common\models\User;

use yii\rest\ActiveController;

use yii\web\Response;

use yii\filters\auth\HttpBearerAuth;

use yii\filters\contentNegotiator;

use yii\filters\AccessControl;

use common\components\Cors;


class ApiController extends ActiveController

{

	public $modelClass='common\models\User';

	

	public function behaviors() {

		$behaviors = parent::behaviors();

		$behaviors['corsFilter'] = [

			'class' => Cors::className(),

			'cors' => [

			   'Origin' => ['*'],

                    //'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],

                    'Access-Control-Request-Headers' => ['Origin', 'X-Requested-With', 'Content-Type', 'accept', 'Authorization'],

                    //'Access-Control-Allow-Credentials' => true,

                    //'Access-Control-Max-Age' => 86400,

                    //'Access-Control-Expose-Headers' => [],			

					//'Access-Control-Request-Headers' => ['Expiry'],

            ],

		];

		$behaviors['authenticator'] = [

			'class' => HttpBearerAuth::className(),

			'only' => ['dashboard'],

		];	

		$behaviors['contentNegotiator'] = [

			'class' => ContentNegotiator::className(),

			'formats' => [

				'application/json' => Response::FORMAT_JSON,

			],

		];

		$behaviors['access'] = [

			'class' => AccessControl::className(),

			'only' => ['dashboard'],

			'rules' => [

				[

					'actions' => ['dashboard'],

					'allow' => true,

					'roles' => ['@'],

				],

			],

		];

		return $behaviors;

	}


	public function actionLogin()

	{

		$model = new LoginForm();

		if ($model->load(Yii::$app->getRequest()->getBodyParams(), '') && $model->login()) {

			return ['access_token' => Yii::$app->user->identity->getAuthKey()];

		} else {

			$model->validate();

			return $model;

		}

	}


(Africanbusinesszone) #2

I have done the same trick.But thanks to the included Cors class.


(Sganz) #3

I just ran into this exact same issue with a React.js app calling Yii2 endpoints.

I tried many of the suggestions in the git Issue tracker until I found this. This is the only thing that seems to work when an ajax call triggers a pre-flight request due to content type. I ran into this while testing with jQuery’s .ajax call as well when the content type is one that triggers a preflight request. The developer that is making the call to the api is using one of the React helpers to make the call and it send the data as JSON I’m thinking, which can’t be sent without the protocol doing a preflight (hope that makes sense).

[font="Arial"]

From the jQuery doc any request that is not one of these type - [font="Courier New"]application/x-www-form-urlencoded, multipart/form-data, or [/font][font="Courier New"]text/plain[/font] will trigger the preflight.

I did basically the same, added your Cors.php to my components directory, then did this in my behaviors method. I’m using this in a regular controller (not Rest) -


	

use app\common\Cors;

...

public function behaviors()

	{

		$behaviors = parent::behaviors();

		$behaviors['corsFilter'] = [

				'class' => Cors::className(), // the new Cors class inherited from yii2's

				'cors' => [

	               'Origin' => ['*'],

					'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],

					'Access-Control-Request-Headers' => ['Origin', 'X-Requested-With', 'Content-Type', 'accept', 'Authorization'],

				],

		];


		return $behaviors;

	}



[/font]

The jQuery test case I do to cause the preflight was -




$.ajax({

	url:	'http://someurl.com/api/endpoint',

	type: 	'post',

	data: {

        	query : '{"some valid test data for your api endpoint"}'


		},


   headers: {

		'X-Requested-With' : 'XMLHttpRequest'  // force preflight

	},


	error: function(data) {

    	console.log(data)

	},


	success: function (envelope) {

    	console.log(envelope)

	}

});



After all that, just wanted to say a HUGE THANKS for the solution!!!

Sandy


(Nges B) #4

I have been working on a similar project using Yii2 API and reactjs. Instead, I need the opposite. I want to authenticated actions in a particular controller. I have written my behaviors as follows
public function behaviors()
{
return array_merge(parent::behaviors(), [
// For cross-domain AJAX request
‘corsFilter’ => [
‘class’ => \yii\filters\Cors::className(),
‘cors’ => [
// restrict access to domains:
‘Origin’=> static::allowedDomains(),
‘Access-Control-Request-Method’ => [‘POST’,‘GET’,‘PUT’,‘OPTIONS’, ‘PATCH’,‘DELETE’],
‘Access-Control-Allow-Credentials’ => true,
‘Access-Control-Max-Age’ => 3600,// Cache (seconds)
‘Access-Control-Request-Headers’ => [‘Origin’, ‘X-Requested-With’, ‘Content-Type’, ‘accept’, ‘Authorization’],
//‘Access-Control-Allow-Credentials’ => true,
‘Access-Control-Allow-Origin’ => false,
],
],
//authenticate the user before execute the actions
‘authenticator’=>[
‘class’ => CompositeAuth::className(),
‘authMethods’ => [
HttpBasicAuth::className(),
HttpBearerAuth::className(),
//QueryParamAuth::className(),
],
‘except’ => [‘search’],
],

    ]);
}

and In my react , I have the following request
const requestOptions = {
method: ‘POST’,
headers: {
‘Authorization’: 'Bearer ’ + user.auth_key,
‘Content-Type’: ‘application/json’
},
body: JSON.stringify(userId)
};
//console.log(‘we are about to login’);
return fetch(apiConstants.API_POST_URL, requestOptions)
.then(handleResponse)
.then(json => {
console.log(json);
return json;
});

but I get unauthorized access hence blocked blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. I do not know how to fix this, any heads on will be great


(Dniznick) #5

Glad it worked for you. I credit the author of the PR request for the concept.