Authorization

Introduction

In addition to providing authentication services, Framework provides a simple way to authorize user actions against resources. Framework offers two primary mechanisms for authorizing user actions: Gates and Policies.

Gates provide a simple closure-based approach to authorization, while Policies group authorization logic around a specific Obvious model or resource.

Gates

Defining Gates

Gates are closures that determine if a user is authorized to perform a given action. Gates are defined within the boot method of App\Providers\AuthServiceProvider using the MacropaySolutions\Kernel\Contracts\Auth\Access\Gate contract resolved from the container.

namespace App\Providers;

use App\Models\Post;
use App\Models\User;
use MacropaySolutions\Kernel\Contracts\Auth\Access\Gate;
use MacropaySolutions\Kernel\Support\ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Register authorization services.
     */
    public function boot(): void
    {
        $gate = \app(Gate::class);

        $gate->define('update-post', function (User $user, Post $post): bool {
            return $user->id === $post->user_id;
        });
    }
}

Authorizing Actions via Gates

To authorize an action using gates, use allows or denies on the resolved Gate instance, or $request->user()->can() and $request->user()->cannot():

namespace App\Http\Controllers;

use App\Models\Post;
use MacropaySolutions\Kernel\Contracts\Auth\Access\Gate;
use MacropaySolutions\Kernel\Http\Request;
use MacropaySolutions\Kernel\Http\Response;

class PostController extends Controller
{
    /**
     * Update the specified post.
     */
    public function update(Request $request, int|string $id): Response
    {
        $post = Post::query()->findOrFail($id);
        $gate = \app(Gate::class);

        if ($gate->denies('update-post', $post)) {
            \abort(403, 'Unauthorized action.');
        }

        $post->fill([
            'title' => (string) $request->getFiltered('title'),
        ])->save();

        return \response()->json([
            'status' => 'success',
            'data' => $post->toArray(),
        ]);
    }
}

Gate Responses

Instead of boolean values, gates may return an MacropaySolutions\Kernel\Auth\Access\Response instance to convey detailed error messages or HTTP status codes:

use App\Models\User;
use MacropaySolutions\Kernel\Auth\Access\Response;
use MacropaySolutions\Kernel\Contracts\Auth\Access\Gate;

$gate = \app(Gate::class);

$gate->define('edit-settings', function (User $user): Response {
    return $user->isAdmin
        ? Response::allow()
        : Response::denyWithStatus(404);
});

Intercepting Gate Checks

To grant all abilities to a specific user type (such as administrators), define a before callback:

use App\Models\User;
use MacropaySolutions\Kernel\Contracts\Auth\Access\Gate;

$gate = \app(Gate::class);

$gate->before(function (User $user, string $ability): bool|null {
    if ($user->isAdministrator()) {
        return true;
    }

    return null;
});

Policies

Creating Policies

Policies are classes that organize authorization logic around a specific Obvious model. Generate a policy using the run console command:

php run make:policy PostPolicy --model=Post

Registering Policies

Register policies in your AuthServiceProvider using the policy method on the Gate contract:

namespace App\Providers;

use App\Models\Post;
use App\Policies\PostPolicy;
use MacropaySolutions\Kernel\Contracts\Auth\Access\Gate;
use MacropaySolutions\Kernel\Support\ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Register authorization services.
     */
    public function boot(): void
    {
        $gate = \app(Gate::class);

        $gate->policy(Post::class, PostPolicy::class);
    }
}

Writing Policies

Policy methods receive an authenticated User instance as their first parameter and the target model as their second parameter:

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    /**
     * Determine if the post can be updated by the user.
     */
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}

Guest Users

By default, gates and policies return false if the incoming request is unauthenticated. To allow unauthenticated checks, make the user argument nullable:

public function update(?User $user, Post $post): bool
{
    return $user?->id === $post->user_id;
}

Policy Filters

Define a before method on a policy class to run pre-authorization checks:

public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true;
    }

    return null;
}

Authorizing Actions Using Policies

Via the User Model

The App\Models\User model provides can and cannot methods:

if ($request->user()->cannot('update', $post)) {
    \abort(403);
}

Via Controller Helpers

Base controllers providing the ProvidesConvenienceMethods trait include an authorize helper. If unauthorized, an AuthorizationException is thrown and captured by the exception handler to format a 403 JSON response:

namespace App\Http\Controllers;

use App\Models\Post;
use MacropaySolutions\Kernel\Http\Request;
use MacropaySolutions\Kernel\Http\Response;

class PostController extends Controller
{
    /**
     * Update the specified post.
     */
    public function update(Request $request, int|string $id): Response
    {
        $post = Post::query()->findOrFail($id);

        $this->authorize('update', $post);

        $post->fill([
            'title' => (string) $request->getFiltered('title'),
        ])->save();

        return \response()->json([
            'status' => 'success',
            'data' => $post->toArray(),
        ]);
    }
}

This site uses Just the Docs, a documentation theme for Jekyll.