I have developed Angular & Yii2 REST service. Have problem in cross domain.
Here below add my angular & Yii2 REST Code.
AngularJs : (like 'http://organization1.example.com','http://organization2.example.com',....)
$http.defaults.useXDomain = true;
$http.defaults.withCredentials = true;
$http.defaults.headers.common['Authorization'] = 'Bearer ' + MYTOKEN
My Request from Angular Controller:
apiURL = 'http://api.example.com';
$http.get(apiURL + '/roles')
.success(function (roles) { })
.error(function () { });
Yii2 .htaccess: (REST URL like 'http://api.example.com')
Header always set Access-Control-Allow-Origin: "*"
Header always set Access-Control-Allow-Credentials: true
Header always set Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Authorization,X-Requested-With, content-type"
Yii2 My Behaviour:
public function behaviors() {
$behaviors = parent::behaviors();
$behaviors['corsFilter'] = [
'class' => Cors::className(),
'cors' => [
'Origin' => ['*'],
'Access-Control-Expose-Headers' => [
'X-Pagination-Per-Page',
'X-Pagination-Total-Count',
'X-Pagination-Current-Page',
'X-Pagination-Page-Count',
],
],
];
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['options'],
];
$behaviors['contentNegotiator'] = [
'class' => ContentNegotiator::className(),
'formats' => [
'application/json' => Response::FORMAT_JSON,
],
];
return $behaviors;
}
Problem
From my angular request is 'GET' method, but it will goes 'OPTIONS' method & return 401 Unauthorized error(CORS). because the request Authorization header is not send.
Update:
As pointed by @jlapoutre, this is now well described in official docs:
Adding the Cross-Origin Resource Sharing filter to a controller is a
bit more complicated than adding other filters described above,
because the CORS filter has to be applied before authentication
methods and thus needs a slightly different approach compared to other
filters. Also authentication has to be disabled for the CORS Preflight
requests so that a browser can safely determine whether a request can
be made beforehand without the need for sending authentication
credentials. The following shows the code that is needed to add the
yii\filters\Cors filter to an existing controller that extends from
yii\rest\ActiveController:
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
// remove authentication filter
$auth = $behaviors['authenticator'];
unset($behaviors['authenticator']);
// add CORS filter
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::className(),
];
// re-add authentication filter
$behaviors['authenticator'] = $auth;
// avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
$behaviors['authenticator']['except'] = ['options'];
return $behaviors;
}
Old Answer (deprecated)
There is an ordering issue when merging with parent::behaviors()
. Full details here.
I would recommend not defining keys when merging with parent array:
public function behaviors()
{
return \yii\helpers\ArrayHelper::merge([
[
'class' => \yii\filters\Cors::className(),
'cors' => [...],
],
[
'class' => \yii\filters\auth\HttpBearerAuth::className(),
'except' => ['options'],
],
[
'class' => ContentNegotiator::className(),
'formats' => [...],
]
], parent::behaviors());
}
In your controller:
use yii\filters\Cors;
...
public function behaviors()
{
return array_merge([
'cors' => [
'class' => Cors::className(),
#special rules for particular action
'actions' => [
'your-action-name' => [
#web-servers which you alllow cross-domain access
'Origin' => ['*'],
'Access-Control-Request-Method' => ['POST'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => null,
'Access-Control-Max-Age' => 86400,
'Access-Control-Expose-Headers' => [],
]
],
#common rules
'cors' => [
'Origin' => [],
'Access-Control-Request-Method' => [],
'Access-Control-Request-Headers' => [],
'Access-Control-Allow-Credentials' => null,
'Access-Control-Max-Age' => 0,
'Access-Control-Expose-Headers' => [],
]
],
], parent::behaviors());
}
Documentation