user6168095
user6168095

Reputation:

Laravel policies strangely not working

The point is simple: I have a UserPolicy method that checks if a user wants to edit his/her own profile. So I did like this:

public function update(User $user, User $model)
{
    return $user->id === $model->id;
}

And this is called in a UserController as it follows:

public function edit(User $user)
{
    $this->authorize('update', $user);
    return view('users.edit')->with('user', $user);
}

Everything is the same in a PostController and a PostPolicy, meant to check if a user can edit his/her own post and it works. The only difference is in their signature, since one has two users (the first one is the currently authenticated user injected by Laravel and the other is the instance I want to check it with) and the other has the above seen automatically injected authenticated user and a post instance. Anyway, it throws:

Symfony \ Component \ HttpKernel \ Exception \ AccessDeniedHttpException

This action is unauthorized.

I tried to dd($model) but I got the same exception. Why? Thanks in advance!

EDIT

In my AuthServiceProvider is all set up, too:

    protected $policies = [
    // 'App\Model' => 'App\Policies\ModelPolicy',
    Post::class => PostPolicy::class,
    User::class => UserPolicy::class,
];

And so is my routes.php:

// Authentication Routes...
$this->post('login', 'Auth\LoginController@login')->name('login');
$this->post('logout', 'Auth\LoginController@logout')->name('logout');

// Registration Routes...
$this->post('register', 'Auth\RegisterController@register')->name('register');

// Password Reset Routes...
$this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
$this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController@reset');

Route::get('/', 'HomeController@index')->name('home');

Route::resource('posts', 'PostController');

Route::resource('users', 'UserController')->except('index', 'create', 'store');

Everything above is called right here:

@if ($user->id == Auth::id())
    <a class="btn btn-link float-right p-0"
    href="{{ route('users.edit', Auth::id()) }}">

        <i class="fas fa-cog"></i>
        Edit profile
    </a>

    <br><br><br>
@endif

Upvotes: 4

Views: 13475

Answers (3)

Vipin
Vipin

Reputation: 61

Few Points to remember/check.

  1. While registering your Policy in App\Providers\AuthServiceProvider if you are using the class names of your model and policy, remember to include the references to your Model & Policy Class using Use Statements like below. Otherwise use full path of Model & Policy classes.

    
    namespace App\Providers;
    
     use App\Models\Post;
     use App\Policies\PostPolicy;
     use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
    
     class AuthServiceProvider extends ServiceProvider
     {
    
         protected $policies = [
             Post::class => PostPolicy::class,
         ];
    
         public function boot(): void
         {
             $this->registerPolicies();
    
             //
         }
     }
     
  2. Remember to Call authorize method like, $this->authorize('create', Post::class); in your Controller methods with required class or model as required, and check if you are using the correct classes or models in policy methods as they are passed. Policy methods like create and viewAny requires the class to determine which policy should be used, other may require model to be passed like, $this->authorize('update', $post);

Or you can use your controller's __construct() method to map predefined policy methods with methods of constructors like below, if your Controller and Policy both created using the --model flag and follows the required method signatures and type. Otherwise you should explicitly call authorize() method.



    namespace App\Http\Controllers;
 
    use App\Http\Controllers\Controller;
    use App\Models\Post;
    use Illuminate\Http\Request;
 
    class PostController extends Controller
    {
      public function __construct()
      {
          $this->authorizeResource(Post::class, 'posts');
      }
    }
  1. Check if you request goes through auth middleware which uses the Laravel's Illuminate\Auth\Middleware\Authenticate class by protecting your Routes like below.

    Route::get('/posts/create', function () { })->middleware('auth');

You can also do so by calling middleware in your controller __construct() method like below.


    class PostsController extends Controller
    {
    public function __construct()
    {
        return $this->middleware('auth'));
    }
    ...

Upvotes: 2

lijishan
lijishan

Reputation: 48

I just solved the same issue after fighting a whole day. Using full paths for register did not work for me. I fixed it by modifying my routes. I post my solution here hoping it may help someone someday.

If your routes are not protected by the authentication middleware, an AccessDeniedException will be thrown before applying your policies. The reason is that if your request comes in directly, you will never be treated as a logged-in user, so that you will be kicked off when trying to call $this->authorize('update') within the controller.

Route::middleware("auth:sanctum")->group(function () {
    Route::post('/member/{id}', [MembersController::class, 'update']);
    // ... and other path.
});

Upvotes: 0

user6168095
user6168095

Reputation:

I'm giving an answer myself: I tried to write the model and policy's full paths instead of registering the policies by the classes' names and it works (I don't know why, of course).

I did like this:

protected $policies = [
    // 'App\Model' => 'App\Policies\ModelPolicy',
    'App\User' => 'App\Policies\UserPolicy',
    'App\Post' => 'App\Policies\PostPolicy',
];

Anyway, thanks everyone for trying to help me. Hope it will help someone else one day!

Upvotes: 8

Related Questions