Yii2 Rest - individual action and OPTIONS method

I'm having problems with the following "/ login" action in my UsersController class

public function actionLogin(){ $data = Yii::$app->getRequest()->getBodyParams(); $model = new Usuario(); //Validamos que se hayan recibido los campos if(empty($data['email']) || empty($data['password'])){ throw new \yii\web\BadRequestHttpException("Debe ingresar email y password"); } //Validamos usuario y contraseña $usuario = $model->findByUsername($data['email']); if(empty($usuario) || !$usuario->validatePassword($data['password'])){ throw new \yii\web\UnauthorizedHttpException("Usuario y/o contraseña incorrectos"); } return $usuario; } 

The situation is that I use the POST method to log in, and I call this route from another domain, so first the frontend library tries to call / login route using the OPTIONS method to check if it is allowed or not to call / log in POST ..

The problem is that yii2 rest ActiveController built-in functions are only for / users and / users / {id}

If I manually add this / login to access POST and OPTIONS through verbFilter actions, then yii tries to actually invoke the login action with the OPTIONS request. I mean, he is trying to log in. Of course, he cannot, because he does not send email and password fields, but I see an error in the log file.

So my question is: ... Is there a way to properly configure these “custom” routes and make OPTIONS transparent? Since I expect that the login action will not be executed when called with OPTIONS, but instead will directly return the headers of the methods allowed by OPTIONS.

Update Info: Added URL Manager Rules

 'urlManager' => [ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => true, 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['v1/users'], 'pluralize' => false, 'tokens' => [ '{id}' => '<id:\\w+>' ] ], //Rutas usuario 'v1/login' => '/v1/users/login' ], ], 
+7
rest php yii2
source share
5 answers

By default, the yii\rest\UrlRule will apply these patterns to any endpoint:

 'patterns' => [ 'PUT,PATCH {id}' => 'update', 'DELETE {id}' => 'delete', 'GET,HEAD {id}' => 'view', 'POST' => 'create', 'GET,HEAD' => 'index', '{id}' => 'options', '' => 'options', ] 

This means that any request containing OPTIONS commands will be redirected to yii\rest\OptionsAction if your loginAction written inside a class that extends ActiveController .

I suggest redefining patterns , leaving only the verbs used, since your login action does not require any other CRUD actions. this should work with your case:

 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['v1/users'], 'pluralize' => false, 'tokens' => [ '{id}' => '<id:\\w+>' ] ], [ 'class' => 'yii\rest\UrlRule', 'controller' => ['v1/login' => '/v1/users/login'], 'patterns' => [ 'POST' => 'login', '' => 'options', ] ] ], 

NOTE. @CreatoR's solution is also a requirement here, and just like it, without defining keys. otherwise, OPTIONS arguments will be rejected if they are not authenticated.


If your login action is defined in a class that extends yii\rest\Controller directly, and not through yii\rest\ActiveController (which should be suitable for authentication actions, since CRUD is not required here), then the same rules should work fine , but you need to manually add actionOptions to your code:

 // grabbed from yii\rest\OptionsAction with a little work around private $_verbs = ['POST','OPTIONS']; public function actionOptions () { if (Yii::$app->getRequest()->getMethod() !== 'OPTIONS') { Yii::$app->getResponse()->setStatusCode(405); } $options = $this->_verbs; Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $options)); } 
+5
source share

I solved the problem by extending the Cors filter class:

 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; } } 

and then

 public function behaviors() { $behaviors = parent::behaviors(); unset($behaviors['authenticator']); $behaviors['corsFilter'] = [ 'class' => CorsCustom::className(), ]; $behaviors['authenticator'] = [ 'class' => HttpBearerAuth::className(), 'optional' => ['login'] ]; return $behaviors; } 
+2
source share

I had the same problem.

Here is how I fixed it:

I added the extraPatterns parameter to my rule, for example:

  'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'pluralize' => false, 'controller' => [ 'athlete', 'admin', 'address', 'program' ], 'extraPatterns' => [ 'OPTIONS <action:\w+>' => 'options' ] ] ], ], 

This way, the options action will be called for every custom action that I have in any of these controllers.

+1
source share

You need to attach the Cors filter to your behaviours() controller (see the official manual ) with the following conditions:

  • Cors filter must be defined before the authentication / authorization filters.
  • Open access for all users to the option action in the AccessControl filter

In your situation, UsersController may have a behaviors() method like this:

 public function behaviors() { return ArrayHelper::merge( [ 'cors' => [ 'class' => Cors::className(), ], ], parent::behaviors(), [ 'access' => [ 'class' => AccessControl::className(), 'rules' => [ ['allow' => true, 'actions' => ['options']], ] ], ] ); } 
0
source share
 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['v1/users'], 'pluralize' => false, 'tokens' => [ '{id}' => '<id:\\w+>' ] ], [ 'class' => 'yii\rest\UrlRule', 'controller' => ['v1/login' => '/v1/users/login'], 'patterns' => [ 'POST' => 'login', 'OPTIONS' => 'options', ] ] ], 
0
source share

All Articles