Authenticator behavior optional does not work

in my Yii REST ActiveController:

<?php

    namespace backend\components;

use backend\models\Order;
use yii\filters\auth\HttpBearerAuth;
use yii\helpers\VarDumper;
use yii\web\ForbiddenHttpException;

class ActiveController extends \yii\rest\ActiveController
{
    public function behaviors()
    {
        $behaviors = parent::behaviors();

        $behaviors['authenticator'] = [
            'class' => HttpBearerAuth::class,
            'only' => ['create', 'delete', 'update'],
            'optional' => ['index', 'view'],
        ];

        return $behaviors;
    }

    /**
     * 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 Order $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 = [])
    {
        if (in_array($action, ['update', 'delete']) && $model->created_by !== \Yii::$app->user->id) {
            throw new ForbiddenHttpException('You do not have permission to change this record');
        }
    }
}

the optional doesn’t work and:

    public function getIsFavoriteBook()
    {
        var_dump(Yii::$app->user->isGuest);
        die;

        if (!Yii::$app->user->isGuest) {
            return $this->getFavoriteBooks()->andWhere(['user_id' => Yii::$app->user->id])->exists();
        }

        return null;
    }

the Yii::$app->user->isGuest return false when trying one of the optional [‘index’, ‘view’]

it’s like the optional is totally ignored

First you have told it to apply only to create, delete and update. And so it does!
Scondly getIsFavoriteBook() is treated as just method. All controller actions are prefixed with actionMethodName so it should be actionFavoriteBook() instead and access it in url as active/favorite-book`

Naming your controller ActiveController is also very poor naming!

I suggest you go back to the basics in Yii guide then come back to REST part!

Thanks for reply:
I am naming AcitveController as that so it can be general for all REST Controllers to extend from:
the getIsFavoriteBook() is within the book Model and I add it to fields() so every GET /books returns list of books I can’t seem to know why optional doesn’t work in index and view even though I am requesting them

What I did was adding isFavorite field to /books endpoint when it is request with GET What am I missing here that is making this not work.

I am doing isFavorite() in the fields() related to Book Model so isFavorite status can be returned whether the User is logged in or not.

What I understand is that BookController is using the Book Model to fetch Data when I make request to /books or /books/$id what exactly is preventing the Authentication Behavior from applying it to only the index and view

I am very desperate to know the reason why it didnt’ work. :sob: :sob:

BaseController or BaseRestController or something descriptive is better than confusing Yii-like name

1 Like

Can you explain exactly what does not work, because if you hit GET then you should have the list. It is just how it is supposed to work!

1 Like

Can you post your controller and model, full code?

1 Like

here is the Controller and Model and resources Directorys I don’t use frontend because I am going for RESTful Flutter Application

What supposed to happen is when GET /books returns a list of books as I am making to call to index action as I understand from the Docs.
I am making isFavoriteBook method in my Book Model so That I can return if the user favorites the book or not.

What I want to achieve is that if the user sent access_token return the books with isFavoriteBook as true/false.
if use is not authenticated just show the books and return isFavoriteBook as null.

what am I missing here is what is confusing me

Reading from your code, I think you missed some basics. You want to write your completely custom methods yet you do gymnastics with yi\rest\ActiveController. You should instead be using yii\rest\Controller as your base class.

Active controller automatically gives you CRUD as explain in its docs.

Here is sample code with yii\rest\Controller

<?php
namespace app\controllers;

use app\models\FavoriteBook;
use app\models\Book;
use Yii;

class FavoriteBookController extends \yii\rest\Controller
{

    public function actionCreate()
    {
        $model = new FavoriteBook();
        $model->user_id = Yii::$app->user->id;
        $model->book_id = Yii::$app->request->post(Book::BOOK_ID);

        if ($model->save()) {
            return [
                'success' => true,
                'message' => Yii::t('app', 'Successfully created a fav Book'),
                'model' => $model,
            ];
        } else {
            return [
                'success' => false,
                'message' => Yii::t('app', 'Failed to create  a fav Book'),
                'model' => null,
            ];
        }
    }

    public function actionDelete($id)
    {
        $model = $this->findModel($id);
        if ($model->delete()) {
            return [
                'success' => true,
                'message' => Yii::t('app', 'Book Removed from favorite successfully'),
            ];
        } else {
            return [
                'success' => false,
                'message' => Yii::t('app', 'Failed to remove fav Book'),
            ];
        }
    }

    // Helper method to find the FavoriteBook model
    /**
     * @param $id
     * @return mixed
     */
    protected function findModel($id)
    {
        $model = FavoriteBook::findOne(['book_id' => $id, 'user_id' => Yii::$app->user->id]);

        if ($model === null) {
            throw new NotFoundHttpException('Favorite book not found.');
        }

        return $model;
    }
}

Then send POST to /favorite-book/create and /favorite-book/delete?id=1 to delete fav book with id of 1

1 Like

Still ActiveController is a bad and confusing name. I hope it does not take some marks off the table by your professor!

1 Like

Thanks for the Assistance I like your approach as it’s very organized (I am doing my University Graduating Project it’s very simple but I am learning alot).

What I understand is that ActiveController By Yii seems to override my actions even in Authentication it seems Yii REST Controller I think will be much more clean.

but I still can’t get that requesting GET /books doesn’t return the isFavorite with it.

Look at resource/book.php that extends \backend\models\Book:

public function fields()
  {
      return array_merge(parent::fields(), ['categories', 'authors', 'isFavoriteBook']);
  }

in my model/Book.php:

    public function getIsFavoriteBook()
    {
        
        if (!Yii::$app->user->isGuest) {
            return $this->getFavoriteBooks()->andWhere(['user_id' => Yii::$app->user->id])->exists();
        }

        return null;
    }

What I don’t get it is why the Auth in my ActiveController in my components ignores the optional
if you can just make understand what’s going on that can save my day I don’t like just writing code without understanding a thing.

How is the JSON returned supposed to look like? And how does it currently look like?

I get the following response when accessing the /books when putting all the actions in the only with no optional :
Access_token is included in the request

When I put the index,view in the optional I got the following:
no access_token given here

with access_token (This is here wrong response as I gave valid access_token):

I didn’t notice this but using user session in model is wrong.
Model should not deal with or know directly about user session. That is for the controller.
Refactor this code to controller and see. It provides clarity and simplicity.

Move the code, post latest controller/model code if it does not work

it’s not about simplicity I am just wondering what is the purpose that getIsFavoriteBook doesn’t return what it supposed to return