Authorising Laravel Nova actions

Nova uses Laravel policies to manage resource authorisation. This can be problematic when it comes to resource actions.

For example, you may wish to prevent a user from editing a resource directly, but allow her to update it by running an action.

A simple solution

The solution is to attach a callback to the $runCallback action property.

<?php

use Illuminate\Http\Request;
use Laravel\Nova\Actions\Action;

class UpdateResource extends action
{
    public function __construct()
    {
        $this->runCallback = function (Request $request, $model) {
            return true;
        };
    }
}

How does this work?

Nova uses the Action::authorizedToRun method to determine whether an action may be executed.

public function authorizedToRun(Request $request, $model)
{
    return $this->runCallback
        ? call_user_func($this->runCallback, $request, $model)
        : true;
}

If the $runCallback instance property is truthy, Nova assumes it’s a callback. The value returned by the callback controls whether the action is executed.

Why bother with a callback?

You may be wondering why we need to bother with $runCallback at all. If we don’t define a callback function, authorizedToRun returns true anyway.

The answer lies in the Action::filterByResourceAuthorization method, which discards models the user is not permitted to update. Here’s the relevant chunk of code.

protected function filterByResourceAuthorization(ActionRequest $request)
{
    if ($request->action()->runCallback) {
        $models = $this->mapInto($request->resource())->map->resource;
    } else {
        $models = $this->mapInto($request->resource())
            ->filter->authorizedToUpdate($request)->map->resource;
    }

    // ...more code
}

If $runCallback is falsey, Nova runs the Resource::authorizedToUpdate method. This, in turn, checks the Laravel model policy. Precisely what we don’t want to happen.

Destructive actions

Unfortunately, Nova treats “destructive” actions differently. Here’s the remainder of the filterByResourceAuthorization method:

protected function filterByResourceAuthorization(ActionRequest $request)
{
    // Previous code omitted...

    $action = $request->action();

    if ($action instanceof DestructiveAction) {
        $models = $this->mapInto($request->resource())
            ->filter->authorizedToDelete($request)->map->resource;
    }
}

Unlike “non-destructive” actions, Nova does not check for a callback; it goes straight to the model policy. As such, the $runCallback workaround does not work for destructive actions (despite some claims to the contrary).

Until Nova adds support for a “delete callback”, destructive actions will continue to be controlled to the model policy.

Sign up for my newsletter

A monthly round-up of blog posts, projects, and internet oddments.