Skip to content

Authorization

Overview

Authorization is what ensures an entity has the ability to perform a given task. Laravel provides gates and policies — they are simple but powerful ways to answer this problem.

Authorization needs to be performed on the server. This is usually done through User#can or Gate::authorize. Unfortunately, this is not accessible when working in single-file components.

Hybridly solves this issue by providing a decorator around data objects. This decorator makes policies' actions typeable, so they can be used in single-file component by the can function.

Using data resources

First, a data object extending Hybridly\Support\Data\DataResource needs to be created. This class exposes an $authorizations array which should contain the names of the actions that need to be exposed.

php
<?php

namespace App\Data;

use Carbon\Carbon;
use Hybridly\Support\Data\DataResource;

final class ChirpData extends DataResource
{
    public static array $authorizations = [
      'comment',
      'like',
      'unlike',
      'delete'
    ];

    public function __construct(
        public readonly string $id,
        public readonly ?string $body,
        public readonly int $likes_count,
        public readonly int $comments_count,
        public readonly Carbon $created_at,
    ) {}
}

When transforming a data resource, a lazy authorization property will be appended to the resulting array.

This property will contain a key for each defined policy action, and will be evaluated through Gate::allows:

json
{
	"id": "1514",
	"body": "Ad nihil provident rem voluptatem quis modi harum ad. Tenetur sunt nisi libero qui debitis.",
	"likes_count": 1,
	"comments_count": 3,
	"created_at": "2022-10-12T17:44:05+00:00",
	"authorization": {
    "comment": false,
    "like": false,
    "unlike": true,
    "delete": false
  }
}

The policy for the previous example could look like that:

php
use App\Models\Chirp;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class ChirpPolicy
{
    use HandlesAuthorization;

    public function comment(User $user): bool
    {
        return true;
    }

    public function delete(User $user, Chirp $chirp): bool
    {
        return $chirp->author->is($user);
    }

    public function like(User $user, Chirp $chirp): bool
    {
        return !$user->hasLiked($chirp);
    }

    public function unlike(User $user, Chirp $chirp): bool
    {
        return $user->hasLiked($chirp);
    }
}

Authorizing on the front-end

When sharing a property from a data resource to the front-end, authorizations could directly be checked against the data object, but the can util provides a better syntax.

ts
import { can } from 'hybridly'

const $props = defineProps<{
  chirp: App.Data.ChirpData
}>()

// With the `can` util (recommended)
const canComment = can($props.chirp, 'comment')

// As-is
const canComment = $props.chirp.authorization.comment

Avoid processing authorizations

The method Hybridly uses to provide automatic authorizations has a drawback: the gate is called for each action, each time the data object is serialized.

Fortunately, this is built on top of laravel-data's lazy properties, which mean you can simply call ->exclude('authorization') for them to not be processed.

php
public function show(Chirp $chirp)
{
    $this->authorize('view', $chirp);

    return hybridly('chirps.show', [
        'chirp' => ChirpData::from($chirp)->exclude('authorization'),
    ]);
}

Using custom creation methods

When using a custom from method, the pipeline that resolves authorizations will not be used.

Because of this, you will have to manually call the static resolveAuthorizationArray method when instanciating your data object:

php
final class ChirpData extends DataResource
{
    public static array $authorizations = [
		    'comment',
		    'like',
		    'unlike',
		    'delete'
		];
    
    public function __construct(
        public readonly string $body,
    ) {
    }

    public static function fromModel(Chirp $chirp): static
    {
        return self::factory()
            ->withoutMagicalCreation()
            ->from([
                'body' => $chirp->body,
                'authorization' => static::resolveAuthorizationArray($chirp),
            ]);
    }
}

You may wrap the authorization array in a Lazy property if needed:

php
Lazy::create(fn () => static::resolveAuthorizationArray($chirp))
	->defaultIncluded();