Katalux
Katalux

Reputation: 47

Use Yii2 RBAC for Restful API request

I developed a web-app based on Yii2 Framework. The web-app use the RBAC system for action authorization depending on User Type (Admin, Employee, Sub-Employee). Now i'm developing a mobile-app and i created a new module "mobile" for controllers called by this mobile-app. In these new controller i set the behaviors function with CORS and authenticator and these work with no problem. I also set the RBAC system as i done for the web-app but in the mobile module don't work. Can someone help me setting authorization for controller/action?

public function behaviors()
    {
        $behaviors = parent::behaviors();

        $behaviors['authenticator'] = [
            'class' => CompositeAuth::className(),
            'except' => ['index','view','test'],
            'authMethods' => [
                HttpBearerAuth::className(),
                HttpBasicAuth::className(),
                // QueryParamAuth::className(),
            ],
        ];

        $auth = $behaviors['authenticator'];
        unset($behaviors['authenticator']);

        $behaviors['corsFilter'] =
        [
            'class' => \yii\filters\Cors::className(),
            'cors' => [
                // restrict access to
                'Origin' => ['*'],
                // Allow only POST and PUT methods
                'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
                // // Allow only headers 'X-Wsse'
                'Access-Control-Request-Headers' => ['*'],
                // // Allow credentials (cookies, authorization headers, etc.) to be exposed to the browser
                'Access-Control-Allow-Credentials' => false,
                // // Allow OPTIONS caching
                'Access-Control-Max-Age' => 3600,
                // // Allow the X-Pagination-Current-Page header to be exposed to the browser.
                'Access-Control-Expose-Headers' => ['X-Pagination-Current-Page'],
            ],

        ];



        $behaviors['authenticator'] = $auth;
        // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
        // $behaviors['authenticator']['except'] = ['OPTIONS', 'login'];
        $behaviors['access'] = 
        [
            'class' => AccessControl::className(),
            'rules' => [
                [
                    'allow' => true,
                    'actions' => ['create','view','update','delete','index', 'logout'],
                    'roles' => ['@'],
                    'denyCallback' => function ($rule, $action) {
                        throw new \yii\web\ForbiddenHttpException('You are not allowed to access this page');
                    }
                ],
                [
                    'allow' => true,
                    'actions' => ['login', 'index','test'],
                    'roles' => ['?'],
                    'denyCallback' => function ($rule, $action) {
                        throw new \yii\web\ForbiddenHttpException('You are not allowed to access this page');
                    }
                ],
            ],

        ];

        return $behaviors;
    }

Upvotes: 2

Views: 2166

Answers (1)

Raul Sauco
Raul Sauco

Reputation: 2705

Override the checkAccess() method of ActiveController()

$behaviors['access'] is not the correct way to check access when you are using yii\rest\ActiveController, instead you are expected to override the checkAccess() method.

The documentation is here and here.

An example of how to do it:

/**
 * Checks the privilege of the current user.
 *
 * This method should be overridden to check whether the current user has the privilege
 * to run the specified action against the specified data model.
 * If the user does not have access, a [[ForbiddenHttpException]] should be thrown.
 *
 * @param string $action the ID of the action to be executed
 * @param \yii\base\Model $model the model to be accessed. If `null`, it means no specific model is being accessed.
 * @param array $params additional parameters
 * @throws ForbiddenHttpException if the user does not have access
 */
public function checkAccess($action, $model = null, $params = [])
{
    // You could completely block some actions
    if ($action === 'delete') {

        throw new ForbiddenHttpException(
            Yii::t('app',
                'You are not allowed to {action} client models.',
                ['action' => $action]
            )
        );

    }

    // You could check if the current user has permission to run the action
    if ($action === 'index' && !Yii::$app->user->can('listClients')) {

        throw new ForbiddenHttpException(Yii::t('app',
            'You are not allowed to list clients'));

    }

    // You can also make the check more granular based on the model being accessed
    if ($action === 'view' && 
        !Yii::$app->user->can('viewClient', ['client_id' => $model->id])) {

        throw new ForbiddenHttpException(Yii::t('app',
            'You are not allowed to view client {client}',
            ['client' => $model->id]));            

    }
}

Looking at your example, it seems like you are only checking for authenticated users @ or not-authenticated users, guests, ?.

It is slightly confusing, since it is different on yii\web\Controller, but you should not be checking if the users are authenticated on checkAccess(), that check has already been performed by the authenticator filter, using the code that you posted with your question, by the time checkAccess() gets called, you will, always, have the application user, so @ will always match, and ? will never match.

Since you have commented out the following line:

// $behaviors['authenticator']['except'] = ['OPTIONS', 'login'];

That means that CORS pre-flight requests will always fail, and also that guest users will never be able to login. Any requests that fail authentication will, immediately, result on a 401 unauthorized response.

It seems like you are trying to let all authenticated users access all actions and unauthenticated users access only login, index, and test actions. If that is correct you don't need to use the checkAccess() method, and you can just uncomment the above line and add the actions there, as follows:

$behaviors['authenticator']['except'] = ['options', 'login', 'index', 'test'];

Non authenticated users will be able to access only those actions.

Upvotes: 3

Related Questions