• 5
name

A PHP Error was encountered

Severity: Notice

Message: Undefined index: userid

Filename: views/question.php

Line Number: 191

Backtrace:

File: /home/prodcxja/public_html/questions/application/views/question.php
Line: 191
Function: _error_handler

File: /home/prodcxja/public_html/questions/application/controllers/Questions.php
Line: 433
Function: view

File: /home/prodcxja/public_html/questions/index.php
Line: 315
Function: require_once

name Punditsdkoslkdosdkoskdo

Yii2 Rest - Custom action and OPTIONS method

I'm having trouble with the following action "/login" route 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'm using POST method to perform login, and I'm calling this route from a different domain, so the frontend library first try to call /login route with OPTIONS method to check if it is allowed or not to call /login with POST..

The problem is that the built in functionality of yii2 rest ActiveController is only for /users and /users/{id}

If I manually add this /login route to be available in both POST and OPTIONS through actions verbFilter, then yii Is trying to actually call the login action with the OPTIONS request. I mean, it is trying to perform login. Of course it can't, because it is not sending email and passwords fields, but I can see an error in the log file.

So, my question is... Is there any way to configure correctly this "custom" routes actions and make OPTIONS performs transparently? Because I'm expecting that login action not get executed when calling it with OPTIONS, but instead to return directly the OPTIONS allowed methods headers.

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'
        ],        
    ],

By default the yii\rest\UrlRule class will apply those patterns to any endpoint :

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

Which means that any request holding OPTIONS verbs will be redirected to yii\rest\OptionsAction if your loginAction is written inside a class extending ActiveController.

What I suggest is to override patterns by leaving the only used verbs as your login action doesn't need any of the 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: the solution by @CreatoR is also a requirement here and exactly as he did it without defining keys. otherwise OPTIONS verbs will be rejected if not authenticated.


In case if your login action is defined under a class extending yii\rest\Controller directly instead of passing through yii\rest\ActiveController (which should be appropriate for authentication actions as there is no CRUD needed here) then the same rules configs should work fine but you'll need to manually add the 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));
}
  • 7
Reply Report

I had this same issue.

This is how I fixed it:

I added an extraPatterns parameter to my rule, like this:

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

  • 5
Reply Report
    • This is actually the only answer that helped me. But I also added it to the 'optional' array of the authenticater I use like so: $behaviors['authenticator'] = [ 'class' => sizegjwtJwtHttpBearerAuth::class, 'except' => [ 'options' ], 'optional' => [ 'opt' ] ];

I solved issue by extending 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;
}
  • 4
Reply Report

You need to attach Cors filter in your controller's behaviours() method (see how in official guide) with following conditions:

  1. Cors filter should be defined before Authentication / Authorization filters
  2. Open access for all users to action option in AccessControl filter

In your situation, UsersController might have such behaviors() method:

public function behaviors()
{
    return ArrayHelper::merge(
        [
            'cors' => [
                'class' => Cors::className(),
            ],
        ],
        parent::behaviors(),
        [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                     ['allow' => true, 'actions' => ['options']],
                ]
            ],
        ]
    );
}
  • 0
Reply Report
    • @CreatorR I tried your solution but it didn't worked because, I think, that you didn't understood my question.. Loking at the source code of rest ActiveController, I see that a 'yii estOptionsAction' action is configured for options method.. Is there a way to configure that when calling /login with POST it call actionLogin and when calling /login with OPTIONS then it call 'yii estOptionsAction'? Which is the best way to achieve that? I think that this is what I need..
'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
Reply Report

Warm tip !!!

This article is reproduced from Stack Exchange / Stack Overflow, please click

Trending Tags

Related Questions