Mastering Request Authorization in Laravel: Techniques, Priority, and Best Practices

Nick Ciolpan
Graffino

--

Learn how to implement route middleware, controller method, policy method, gates, and request class to control incoming requests authorization.

In later Laravel versions, every incoming HTTP request can be handled by a request class.

These classes are responsible for validating and sanitizing the request data. Laravel also provides a convenient way to perform authorization checks on a specific request through the authorize method. This method is called automatically by the framework before the request is handled, and it can be used to check if the authenticated user is authorized to perform the requested action.

For example, let’s say we have a simple CRUD application for managing blog posts. We want to ensure that only the owner of a post can update or delete it. To do this, we can create a request class for handling post updates and a separate class for handling post deletions.

use App\\Post;
use Illuminate\\Foundation\\Http\\FormRequest;
class UpdatePostRequest extends FormRequest
{
public function authorize()
{
$post = Post::find($this->route('post'));
return $post && $this->user()->can('update', $post);
}
public function rules()
{
return [
// validation rules
];
}
}

Here, we are using the authorize method to check if the current user is authorized to update the post. We retrieve the post from the route parameters and check if the user has the ability to update it using the can method provided by Laravel's authorization system.

Similarly, we can create a separate request class for handling post deletions with its own authorize method:

use App\\Post;
use Illuminate\\Foundation\\Http\\FormRequest;
class DeletePostRequest extends FormRequest
{
public function authorize()
{
$post = Post::find($this->route('post'));
return $post && $this->user()->can('delete', $post);
}
public function rules()
{
return [
// validation rules
];
}
}

This way we have a specific request class for each type of action, and we can handle the request authorization in a very specific way.

Neat.

However, as your application grows and the number of requests and actions increases, it can become difficult to manage all of the authorization logic in this way. In this scenario, it could be interesting to look into using policies to handle authorization instead.

There are several other methods and locations available to authorize requests, some of which I list below:

Middleware

Laravel’s middleware system allows you to execute code before or after a request is handled. You can create custom middleware to perform authorization checks on a specific route or group of routes. For example, you could create a middleware that checks if the user is an admin before allowing access to certain routes.

class AdminMiddleware
{
public function handle($request, Closure $next)
{
if (! $request->user()->isAdmin()) {
return redirect()->route('home');
}
return $next($request);
}
}

You can then assign this middleware to specific routes or groups of routes in your routes/web.php or routes/api.php file:

Route::middleware(['auth', 'admin'])->group(function () {
Route::get('/admin', 'AdminController@index');
Route::get('/admin/users', 'AdminController@users');
});

Controllers

You can also perform authorization checks within your controllers. This can be useful for simple checks, but as your application grows, it can become difficult to manage all of the authorization logic in this way.

phpCopy code
class PostController extends Controller
{
public function update(UpdatePostRequest $request, $id)
{
$post = Post::find($id);
if (! $request->user()->can('update', $post)) {
return redirect()->route('home');
}
// update the post
}
}

Event Listeners

You can use event listeners to perform authorization checks before an action is performed. For example, you could create a listener that listens for the deleting event on a Post model and check if the user is authorized to delete the post before it is deleted.

class PostDeletingListener
{
public function handle(Deleting $event)
{
if (! $event->user->can('delete', $event->post)) {
throw new \\Exception('Unauthorized.');
}
}
}

You would then need to register this listener in the EventServiceProvider class:

protected $listen = [
Deleting::class => [
PostDeletingListener::class,
],
];

Third-party packages:

There are also several third-party packages available for handling authorization in Laravel such as Entrust, Gatekeeper, and Bouncer.

While Laravel has a built-in authorization system that is powerful and flexible, third-party packages can provide additional functionality and customization options that may better suit the needs of a particular project.

For example, some developers may prefer to use a package like Entrust to manage role-based permissions, while others may prefer a package like Bouncer that provides a more fluent and expressive API for defining authorization rules.

Here’s an example of how you might use Entrust to perform authorization checks in your controllers.

First, you’ll need to install the package using Composer and add the service provider to the providers array in your config/app.php file:

composer require zizaco/entrust
'providers' => [
// ...
Zizaco\\Entrust\\EntrustServiceProvider::class,
],

Next, you’ll need to run the package’s migrations to create the necessary database tables:

php artisan entrust:migration
php artisan migrate

You can now use the Entrust facade to define roles and assign permissions to them:

use Entrust;

class RoleController extends Controller
{
public function create()
{
Entrust::createRole(['name' => 'admin']);
Entrust::createRole(['name' => 'editor']);
}
public function assign()
{
$admin = Entrust::findRoleByName('admin');
$admin->givePermissionTo(['edit-users']);
}
}

You can now use the can and ability methods to perform authorization checks:

class PostController extends Controller
{
public function edit($id)
{
$post = Post::find($id);
if (! auth()->user()->can('update', $post)) {
return redirect()->route('home');
}
// display the edit post form
}
public function update(UpdatePostRequest $request, $id)
{
$this->authorize('update', $post);
// update the post
}
}

The can method allows you to check if the authenticated user has a specific permission, while the ability method allows you to check if a user has a specific permission, regardless of whether they're authenticated or not.

You’re not stuck with one way of doing it. These can be combined. When using multiple methods at once, it’s important to understand the priority order in which Laravel checks for authorization. This can be helpful when troubleshooting or debugging issues that may arise.

  1. Route Middleware: checks if any route middleware is defined for the current route. If a middleware is defined, it will be executed first.
  2. Controller Method: checks if the controller method has an authorize method. If it does, the authorize method will be executed next.
  3. Policy Method: checks if a policy method exists for the action being performed. If a policy method is defined, it will be executed next.
  4. Gate: checks if a gate is defined for the action being performed. If a gate is defined, it will be executed next.
  5. Request Classes: checks if request classes have an authorize method. If it does, the authorize method will be executed next.
  6. Other Packages: checks if any other authorization packages are used for authorization. If it does, it will be executed next.
  7. Global Middleware: checks for any global middleware that might be defined. If any are defined, they will be executed last.

It’s worth noting that if any of the above methods return a false value, the request will be immediately halted, and the user will be redirected to the appropriate page, such as the login page.

Laravel provides a variety of ways to authorize requests, each with its own advantages and use cases. From route middleware to controller method, policy method, gates and request classes, developers have a wide range of options to choose from when implementing authorization in their applications.

--

--

Nick Ciolpan
Graffino

Co-founder of Graffino. I have extensive experience as a full stack developer, managing clients and building state of the art platforms.