ATOM-Group's avatar

"Can" middleware and route resources?

I was reading through the documentation for Authorization using Gates and Policies, and while you can do things like this:

Route::put('/post/{post}', function (Post $post) {
        // The current user may update the post...
})->middleware('can:update,post');

You can't seem to do something like this:

Route::resource('post', PostController::class)->middleware('can');

I've tried this doing Gate::resource(...) and registering a policy, but I can't seem to make it automagically apply the gate/policy to the route resource.

Does anyone know of a way to do this or some other mechanism I may be missing?

I'd like to avoid having to do $request->user()->can(....) conditional checking in my controller methods.

0 likes
6 replies
tykus's avatar

How have you defined the Gate::resource(...) - it should work if you define it properly. You are using the singluar post for your resource whereas we would typical use the plural form:

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

and

Route::resource('posts', App\Policies\PostPolicy::class);
ATOM-Group's avatar

Sorry, yes. This was just a quick example with a typo. The plural conventions/keys I'm using are all correct in my code. But it's not working.

So what you are saying is if I define both

Route::resource('posts', PostController::class); in routes/web.php

and

Gate::resource('posts', App\Policies\PostPolicy::class); in the boot() method of the AuthServiceProvider, then that is all I need to do?

tykus's avatar

What does you PostPolicy look like?

ATOM-Group's avatar

Its kind of long, but basically it's a standard policy created by php artisan make:policy PostPolicy --model="Post"

And the authorization logic is simple:

class PostPolicy
{
        use HandlesAuthorization;

        /**
        * Determine whether the user can view the post.
        *
        * @param  \App\User  $user
         * @param  \App\Post  $post
         * @return mixed
         */
        public function view(User $user, Post $post)
        {
             return $user->id === $post->user_id;
        }

Etc....

clivew's avatar

I'm using Laravel 10 and I've been able to apply my policy to a Route::resource by adding a single line of code to the corresponding controller constructor, as described here in the Laravel Documentation.

In routes/web.php:

...
Route::middleware([
    'auth:sanctum',
    config('jetstream.auth_session'),
    'verified',
])->group(function () {

    Route::resource('clients', ClientController::class)
        ->only(['index', 'store', 'edit', 'update', 'destroy']); 
});
...

In app/Http/Controllers/ClientController.php:

<?php

namespace App\Http\Controllers;

use App\Models\Client;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class ClientController extends Controller
{
    /**
     * Create the controller instance.
     */
    public function __construct()
    {
        $this->authorizeResource(Client::class, 'client');
    }    

    /**
     * Display a listing of the resource.
     */
    public function index(): View
    {
        return view('clients.index', [
            'clients' => Client::all(),
        ]);
    }
...
    /**
     * Show the form for editing the specified resource.
     */
    public function edit(Client $client): View
    {
        return view('clients.edit', [
            'client' => $client,
        ]);
    }
...

In app/Policies/ClientPolicy.php:

<?php

namespace App\Policies;

use App\Models\Client;
use App\Models\User;
use Illuminate\Auth\Access\Response;

class ClientPolicy
{

    /**
     * Determine whether the user can view any models.
     */
    public function viewAny(User $user): bool
    {
        return $user->hasAbilityTo('view clients');
    }

    /**
     * Determine whether the user can view the model.
     */
    public function view(User $user, Client $client): bool
    {
        return $user->hasAbilityTo('view clients');
    }
...
    /**
     * Determine whether the user can update the model.
     */
    public function update(User $user, Client $client): bool
    {
        return $user->hasAbilityTo('update clients');
    }

By the way, I've not needed to add anything to app\Providers\AuthServiceProvider.php.

I hope that's of some use to people arriving at this post.

3 likes
RomainB's avatar

This is the way for Laravel version <= 10.

After that, you need to make your controller extends from Illuminate\Routing\Controller and use the trait Illuminate\Foundation\Auth\Access\AuthorizesRequests:

use App\Models\Client;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Routing\Controller;

abstract class APIController extends Controller
{
    use AuthorizesRequests;

    public function __construct()
    {
        $this->authorizeResource(Client::class);
    }

Please or to participate in this conversation.