OPTIONS REQUEST : Preflight error in angular2 and Yii2 application


(Akhan118) #1

Hi All,

I have been searching the internet for the past week for a bug that I’m currently experiencing, and no luck. the best description that I can find is this one.

(Add handleOptions to Cors filter so OPTIONS requests are handled for the preflight check. #14618).

I’m using angular2 to make a post request to a Yii2 Rest API, the problem that I’m expereining is that the browser send an OPTIONS request first and when it does that , YII2 doesn’t know how to handle it and send back 401 error with a preflight


Request URL:http://localhost/analytic/backend/web/v1/api/test

Request Method:OPTIONS

Status Code:401 Unauthorized

Remote Address:[::1]:80

Referrer Policy:no-referrer-when-downgrade

Response Headers

view parsed






zone.js:2744 OPTIONS http://localhost/analytic/backend/web/v1/api/test 401 (Unauthorized)

dashboard:1 Failed to load http://localhost/analytic/backend/web/v1/api/test: Response for preflight has invalid HTTP status code 401

I looked at the Yii2 documentation and have implemented CORS as follow.





use yii\rest\Controller;

use yii\filters\auth\HttpBearerAuth;


/**

 * Site controller

 */

class ApiController extends Controller

{


      public function behaviors()

    {

        $behaviors = parent::behaviors();

        $behaviors['contentNegotiator'] = [

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

            'formats' => [

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

            ],

        ];

        // 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'];

        $behaviors['authenticator'] = [

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




 ];


        return $behaviors;

    }




}




I am able to authenticate fine if I commented out ‘HttpBearerAuth::className()’ do a post through angular, then uncomment that line then do another post.

example.


          // $behaviors['authenticator'] = [

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

        //     ];



Post through angular, then uncomment those lines.


        $behaviors['authenticator'] = [

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

            ];

the reason why it works when I do that is that the browser sends all following request as POST and not OPTIONS.

Moreover, when I perform the same request with POSTMAN, it works fine with no issues and that’s because POSTMAN sends it as a POST request.

Any help is appreciated. thank you


(Akhan118) #2

I figured it out and in case someone had the same issue as me, here is how to go about it.

Create a custom CORS


<?php

namespace common\helpers;


use Yii;

use yii\filters\Cors;


class CorsCustom extends Cors


{

    public function beforeAction($action)

    {

        parent::beforeAction($action);


        if (Yii::$app->getRequest()->getMethod() === 'OPTIONS') {

            Yii::$app->getResponse()->getHeaders()->set('Allow', 'POST GET PUT');

            Yii::$app->end();

        }


        return true;

    }

}

use it in your REST API controller.


   public function behaviors()

    {

        $behaviors = parent::behaviors();

        $behaviors['contentNegotiator'] = [

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

            'formats' => [

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

            ],

        ];

        // remove authentication filter

        $auth = $behaviors['authenticator'];

        unset($behaviors['authenticator']);

        // add CORS filter

        $behaviors['corsFilter'] = [

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

            ];

        // re-add authentication filter

        $behaviors['authenticator'] = $auth;

        // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)

        $behaviors['authenticator']['except'] = ['options'];

        $behaviors['authenticator'] = [

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

        'except'=>['login']

            ];


        return $behaviors;

    }




(Gzav) #3

Man, thank you. This solved my problem. But not sure why, I had to redeclare the verbs like this in the controller





protected function verbs()

{

    return [

        'index' => ['GET', 'HEAD','OPTIONS'], //instead of  'index' => ['GET', 'HEAD']

        'view' => ['GET', 'HEAD','OPTIONS'],

        'create' => ['POST','OPTIONS'],

        'update' => ['PUT', 'PATCH'],

        'delete' => ['DELETE'],

    ];

}




Maybe because i’ve also redefined these actions.

Anyway, thanks for the input.


(Kyle) #4

Hi Xav,

I had a similar problem. I solved the verb part by creating a token in urlmanager like so:




'tokens' => [

        '{action}' => '<action:[a-zA-Z0-9\\-]+>',

],



And then for the options part you can create an extra pattern like this:




'extraPatterns' => [

      'OPTIONS {action}' => 'options',

],



That way you shouldn’t have to redefine it for every action.