Blog: PHP

Policies and Route::apiResource in Laravel

Published

While I’ve been a PHP developer for 20 years, Laravel is a new area for me, and I must say, am as happy as the proverbial pig. But I did encounter a really head-scratching quirk when working with the authorizeResource call from a Controller.

So here’s the back story… I have a Model called UserDelegation (where a User can delegate responsibilities to another User for a period of time), and that in turn has an associated FormRequest, Resource, Policy and pair of Controllers – one for admin, and one for normal users – you know, all the good practice components.

When creating the UserResource migration, the necessary Permissions are added to the database too so that I can include logic in my UserDelegationPolicy. All hunky dory so far.

In my api.php file, I am adding my routes as an API resource:

Route::apiResource('delegations', 'Manager\UserDelegationController');

When Laravel defines my routes, I am getting the expected GET, POST, DELETE and PUT endpoints for the “delegations” route – which is excellent.

In my controller, in its constructor, am calling authorizeResource

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

But when I try to access the endpoints, I receive a 403 error. Why? My other policies for other Models, such as “Title” or “Organisation” are working as expected. My Policy is using the correct permissions for the new Model, so what gives? Head scratching moment, and Google wasn’t helping, so time to explore the Laravel source to get a better understanding of what is going on.

Looking at the authorizeResource call, this accepts multiple arguments, starting with the Model’s class name. Makes sense so far. But also more arguments, the second called $parameter, which I had never actually used.

This is where the issue stems from, and is definitely a user error.

If you do not include $parameter, the authorizeRequest function will create a snake-case $parameter based on the base name of the Model – so in this instance, it would set $parameter to be “user_delegation”.

However, in my route definitions, Laravel has defined the route parameter as “delegation”, creating a singular representation of my route name. What I mean is that my route looks like:

api/delegations/{delegation}

But you see that authorizeRequest, without explicitly setting $parameter, has “guessed” that it would be “user_delegation”. This means that it would fire for a route like:

api/delegations/{user_delegation}

When I try to call my endpoint, it is not matching because my route is defined with “delegation” but Laravel’s authorisation is expecting “user_delegation”.

How to fix this… well there are two ways to approach this:

  • Explicitly define $parameter with the authorizeResource call
  • Explicitly define the parameter used in the route definition

Be explicit with in the controller

Quite simply, when authorising your Controller, explicitly give it the parameter to look for in the route.

public function __construct()
{
    $this->authorizeResource(UserDelegation::class, 'delegation');
}

While Laravel’s documentation does include reference to this, which I eventually found, to me it didn’t read as obvious as it may be should have. But when you stop and re-read it, it does tell you exactly what you need to know.

Be explicit with in the route definition

This one is a little more wordy to prepare, and means you’re adding the $options array to your route definition, and passing the parameters array with the definition that “delegation” should actually be “user_delegation”.

Route::apiResource('delegations', 'Manager\UserDelegationController', [
    'parameters' => [
        'delegation' => 'user_delegation'
    ]
]);

This makes sense too, and could be useful if you have a really different approach to naming conventions, but does make the api.php routes file less readable (if you ask me). I love the simplicity of my routes file with such clean and easy-to-scan apiResource calls.

Which is better?

Well… I’m undecided – both solve the problem, but as to “better”, that might be more of a coding style question for the project. What do you think? What solution reads better?

I feel that the authorizeResource solution may be easier to write, and a better habit to have, by being explicity with every single authorizeResource call simply passing the $parameter. That seems a simpler best-practice with an easier-to-read api.php routes file. But happy to hear your thoughts too.

Blog

View all
Life

Installing and review of the Sonos Wall Mount with the Sonos One SL

I’m new to the Sonos ecosystem, and after setting up the Sonos Arc and Sub combination, I wanted more, so splurged on a pair of One SL speakers for surrounds....

Continue reading...

Life

IKEA hack: BESTÅ unit with HANVIKEN doors with speaker fabric

There’s a room in the house called the Marty Cave. It’s like a Man Cave, but for Marty. And that’s me (phew) so that’s all good. And in the Marty Cave...

Continue reading...

JS

Learning to love the menu

This article was written for and originally appeared on Blueprint by Tiny. TinyMCE gives you immense flexibility when it comes to the user interface you present...

Continue reading...

Web

Lessons learned over 20 years as a web developer

This article was written for and originally appeared on Blueprint by Tiny. Is it just me, or does it still feel like the 90s happened only yesterday? It feels...

Continue reading...

I am the Development Director (and co-owner) at Mity Digital, a Melbourne-based digital agency specialising in responsive web design, custom web development and graphic design.
Mity Digital