Laravel Policy Always returns 403 unauthorized

1.4k Views Asked by At

I'm making an app that uses jwt as authentication system ,

when I try to update my Category model the policy always returns 403 unauthorized,

I'm using apiResource to crud my model.

my code

in api.php:

Route::apiResource('category', CategoryController::class);

in CategoryController.php:

    public function update(Request $request, $id)
    {
        // print_r($request->all());
        $validator = Validator::make(
            $request->all(),
            [
                'name' => 'required|min:2|unique:categories,name,' . $request->id,
                'description' => 'required|min:1',
            ],
            [
                "name.unique" => "اسم الصنف مستخدم مسبقا",
                "name.required" => "اسم الصنف مطلوب",
                "name.min" => "اسم الصنف يجب أن يحتوي على حرفين على الأقل",

                "description.required" => "وصف الصنف مطلوب",
            ]
        );
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->messages(), 'status' => 422], 200);
        }
        $category = Category::find($id);
        $category->name = $request->name;
        $category->description = $request->description;
        $category->save();
        return response()->json([
            "message" => "تم تحديث الصنف",
            "status" => 200
        ], 200);
    }

in CategoryPolicy.php:

    public function update(User $user, Category $category)
    {

        return $category->user_id === $user->id;
    }

It seems like the request is not even reaching the update method in CategoryPolicy.php because even if the method always returning true it's not working :

    public function update(User $user, Category $category)
    {

        return true;
    } 

any way the viewAny method is working as expected.

I'm using axios to fetch and update data and I'm sending the request with the bearer token and every thing is working ok except the issue above.

2

There are 2 best solutions below

2
On BEST ANSWER

In CategoryController.php, instead of injecting $id:

public function update(Request $request, $id)

Try injecting the type-hinted model instance:

public function update(Request $request, Category $category)

And remove the find() command:

//$category = Category::find($id);

When generating new controllers, you can also use this artisan command to include type-hinted models in the function arguments.

php artisan make:controller CategoryController --api --model=Category
0
On

It's hard to see what is going wrong because it can also be the middleware and JWT token. What you could do is in your update method check if the user is logged in, add the following as the first line in the method. If false please check your JWT implementation

dd(auth()->check());

I would also suggest clean up your controller:

class CategoryController
{
    /**
     * CateogryController constructor.
     */
    public function __construct()
    {
        $this->authorizeResource(Category::class); // if your are using CRUD, validate like this
    }

    /**
     * Update specific resource.
     *
     * @param Category $category
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function update(Category $category, CategoryRequest $request): JsonResponse
    {

// notice the model route binding. 
        $this->authorize('update', $category); // If you only have update method, but remove the __construct.

        $category->update([
            'name' => $request->get('name'),
            'description' => $request->get('description')
        ]);

        return response()->json(['message' => 'تم تحديث الصنف']); // take the 200 from the headers, not add it in as text. 
    }

}

Your request looks similar to this:

class CategoryRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true; // you could consider to validate the user->category relation. I like it more separated and put it in a separated policy.
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|min:2|unique:categories,name',
            'description' => 'required|min:1',
        ];
    }

    /**
     * @return string[]
     */
    public function messages()
    {
        return [
            "name.unique" => "اسم الصنف مستخدم مسبقا",
            "name.required" => "اسم الصنف مطلوب",
            "name.min" => "اسم الصنف يجب أن يحتوي على حرفين على الأقل",
            "description.required" => "وصف الصنف مطلوب",
        ];
    }
}

And your policy like:

class CategoryPolicy
{
    use HandlesAuthorization;

    /**
     * Determine if the user can update category resource.
     *
     * @param User $user
     * @param Category $category
     * @return bool
     */
    public function update(User $user, Category $category): bool
    {
        return $user->categories()->where('id', $category->id)->exists(); // or somthing like this.
    }
}