Laravel authorisation with policies and requests

Laravel comes with a few tools which help you manage authorisation and restrict how different users interact with your application.

The Laravel documentation for Authorisation covers how you handle most of this. First you would create a policy for your model. Laravel has an artisan command to help with that;

php artisan make:policy PostPolicy --model=Post

A common solution is to check the policy rules within the controller. Laravel provides a helper which makes this easy.

Your BaseController probably uses the AuthorizesRequests trait, which has helper methods, such as authorize() and authorizeForUser(). I have added this trait to the example below.

<?php

namespace App\Http\Controllers\Post;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\RedirectResponse as Response;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class DeleteController extends Controller
{
	use AuthorizesRequests;
	
    public function __invoke(Request $request, Post $post): Response
    {
        $this->authorize('delete', $post);

        $post->delete();

        return redirect()->back()->with('status', 'The post has been deleted.');
    }
}

However, I prefer to use custom form requests when dealing with data and validation. Along with rule validation, the form requests support authorisation. Most of the time I have the authorize() method returning true, but now we can look to move the authorisation from the controller to the request.

We can generate a form request using artisan; php artisan make:request DeleteRequest which I have put under the Post namespace.

Here we use the Gate facade to check whether the authorised user is allowed to delete the post. $this->post is available because of route model binding.

<?php

namespace App\Http\Requests\Post;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;

class DeleteRequest extends FormRequest
{
    public function authorize(): bool
    {
        return Gate::allows('delete', $this->post);
    }
}

This solution works, but we could use the AuthorizesRequests trait and keep the syntax similar to the controller authorisation. Below we include the trait, but have to rename the authorize() method to avoid conflicts.

<?php

namespace App\Http\Requests\Post;

use Illuminate\Auth\Access\Response;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Http\FormRequest;

class DeleteRequest extends FormRequest
{
    use AuthorizesRequests {
        authorize as check;
    }

    public function authorize(): Response
    {
        return $this->check('delete', $this->post);
    }
}

Again, this works, but it seems clunky.

Thankfully, Laravel actually provides an easier way. I missed the solution because I was looking under requests and authorisation. Although not listed under the using policies in the authorisation section, the solution is mentioned under the validation documentation. We use the can() method against the authenticated user.

<?php

namespace App\Http\Requests\Post;

use Illuminate\Foundation\Http\FormRequest;

class DeleteRequest extends FormRequest
{
	public function authorize(): bool
	{
	    return $this->user()->can('delete', $this->post);
	}
}

That is simple and easy to understand.


So, I found two different ways of implementing the policy authorisation using form requests before finding the "Laravel way". In total there are four solutions here which all work. This was an interesting exercise which started with "test driven development" (TDD) approach, with a feature test written using the Laravel HTTP Tests.