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.

3 Answers 3

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());
}
Sign up to request clarification or add additional context in comments.

1 Comment

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.
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

Comments

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;

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.