Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Batman55's avatar
Level 32

Policy authorizing logged in user to modify user passed in via route model binding issues

Laravel binds the authorized user to both User models. I've tried changing the first to get a request, but it's not binding and I get an error. I've also tried explicitly expressing the two parameters via the available array through Gate::authorize('update', [\Auth::user(), $user_to_be_updated]); without any luck.

In the policy: public function update(User $request_user, User $model_to_be_updated): bool

0 likes
7 replies
LaryAI's avatar
LaryAI
AI
Best Answer
Level 58

To resolve the issue with authorizing a logged-in user to modify another user using route model binding in Laravel, you need to ensure that your policy method is correctly set up and that you're calling it with the right parameters.

Here's a step-by-step solution:

  1. Define the Policy Method:

    In your UserPolicy, you should have a method like this:

    public function update(User $requestUser, User $modelToBeUpdated): bool
    {
        // Example condition: only allow if the request user is an admin or the same user
        return $requestUser->is_admin || $requestUser->id === $modelToBeUpdated->id;
    }
    

    This method checks if the requestUser is either an admin or the same user as the modelToBeUpdated.

  2. Register the Policy:

    Ensure that your policy is registered in AuthServiceProvider:

    protected $policies = [
        \App\Models\User::class => \App\Policies\UserPolicy::class,
    ];
    
  3. Authorize the Action:

    When you want to authorize the action, you should use the authorize method or the can method provided by Laravel. Here's how you can do it:

    public function update(Request $request, User $user)
    {
        // This will automatically resolve the current authenticated user
        $this->authorize('update', $user);
    
        // Proceed with the update logic
    }
    

    Alternatively, if you want to explicitly pass the authenticated user:

    public function update(Request $request, User $user)
    {
        $requestUser = $request->user(); // or \Auth::user()
        $this->authorize('update', [$requestUser, $user]);
    
        // Proceed with the update logic
    }
    
  4. Route Model Binding:

    Ensure your route is set up to use route model binding:

    Route::put('/users/{user}', [UserController::class, 'update']);
    

    This will automatically inject the User model instance into your controller method.

By following these steps, you should be able to authorize the logged-in user to modify another user using Laravel's policy system. If you encounter any errors, double-check that your policy is correctly registered and that the method signatures match between your policy and how you're calling it.

Batman55's avatar
Level 32

Other than the parameter names, I had this set up to begin with. Parameter names must make a difference in the dependency injection.

martinbean's avatar

@batman55 You can just rename one of the parameters. I usually rename the authenticated user parameter to be actor:

class UserPolicy
{
    public function update(User $actor, User $user): bool
    {
        // Authorisation logic here...
    }
}
1 like
Batman55's avatar
Level 32

@martinbean I'm not sure what was going on. If I had more than one type hinted as a User, it was injecting the authorized user into both. Didn't matter what the parameter names were.

I tried several different variations. I do have other projects where update(User $authUser, User $model) work as expected, providing the authorized user and the model that we are updating.

I usually use the request to get the authorized user (as is normal convention), but even using update(Request $request, User $user) was providing the authorized user instead of the model for User $user.

During this time edit(Request $request, User $user) for this controller was working just fine and all tests for authorization and Gates were passing.

martinbean's avatar

@Batman55 It was because of how you were calling the policy.

This is wrong:

// WRONG
Gate::authorize('update', [\Auth::user(), $user_to_be_updated]);

You don’t need to pass the authenticated user; the authenticated user will be injected automatically. You just need to pass arguments after the authenticated user. For example:

Gate::authorize('update', $userToBeUpdated),

If you read the authorization docs, you’ll notice the example for PostPolicy::update just passes the post as parameters:

Gate::authorize('update', $post); // No need to pass authenticated user

Even though the corresponding policy method will take a user as the first argument:

public function update(User $user, Post $post): bool
{
    // Authorization logic...
}
Batman55's avatar
Level 32

@martinbean you are correct, it is the wrong way to go about it, but updated to take the $request->user it works.

This currently works:

Gate::authorize('update', [$request->user, $user]);

This does not:

Gate::authorize('update', $user);

Previously using Gate::authorize('update', $user); would produce the injection of the request user into both parameters in the policy update(User $requestUser, User $modelToBeUpdated). Now it doesn't do that and produces the current error:

Gate::authorize('update', $user), throws ArgumentCountError: Too few arguments to function App\Policies\UserPolicy::update()

It is currently not injecting the Request or Request user (tried both). This is only happening on Policies that take in two user parameters (one for the request user and one for the model to be modified). If you have a user and a post as the example, it works as expected.

This behavior did start after bumping from 11.31 to 11.44. I don't believe that is the issue, but something wonky is going on.

Update: this wonky behavior was only behaving like this while using vite hot reloading via ```composer run dev``. Running via Herd, it works as it should injecting the request user and model to be updated using:

Gate::authorize('update', $user); and update(User $requestUser, User $modelToBeUpdated)

Batman55's avatar
Level 32

The tests also work correctly when running $this->withoutVite(); in Pest.

Please or to participate in this conversation.