Yii2 REST+ Angular Cross Domain CORS
Asked Answered
L

3

7

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.

Longsuffering answered 12/2, 2016 at 9:47 Comment(0)
R
9

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());
}
Regenerator answered 12/3, 2016 at 22:53 Comment(1)
This is now documented clearly here: yiiframework.com/doc-2.0/guide-rest-controllers.html#cors - just follow this guide and it will work. Summary: make sure authentication comes after CORS behavior and exclude OPTIONS from authentication at all times.Dana
F
1

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

Fincher answered 12/2, 2016 at 19:33 Comment(0)
B
1

issue with your code you are not unsetting auth at very start

public function behaviors() {
$behaviors = parent::behaviors();
/*unset here*/
unset($behaviors['authenticator']);
$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',
        ],
    ],
];
/*re-set here*/
$behaviors['authenticator'] = [
    'class' => HttpBearerAuth::className(),
    'except' => ['options'],
];
$behaviors['contentNegotiator'] = [
    'class' => ContentNegotiator::className(),
    'formats' => [
        'application/json' => Response::FORMAT_JSON,
    ],
];

return $behaviors;

}

Babism answered 9/5, 2019 at 3:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.