whilesmart/eloquent-owner-access

Pluggable polymorphic owner authorization for Laravel packages with owner_type/owner_id columns. Lets host apps decide who can see and modify owner-scoped records without coupling the underlying packages to a specific tenancy model.
1,479
Install
composer require whilesmart/eloquent-owner-access
Latest Version:1.0.0
PHP:^8.2
License:MIT
Last Updated:Jun 10, 2026
Links: GitHub  ·  Packagist
Maintainer: whilesmart

eloquent-owner-access

Pluggable polymorphic owner authorization for Laravel packages with owner_type / owner_id columns.

Domain packages (accounts, expenses, payments, invoices, products, customers, ...) expose CRUD endpoints scoped by a polymorphic owner. They should not have to know how the host app decides who owns what. This package gives them a single contract to consult and a default that preserves existing "trust the client" behavior, so adoption is incremental and safe.

Concept

One contract:

interface OwnerAuthorizer
{
    public function authorize(?Authenticatable $user, string $ownerType, mixed $ownerId): bool;

    public function scope(
        Builder $query,
        ?Authenticatable $user,
        string $ownerTypeColumn = 'owner_type',
        string $ownerIdColumn = 'owner_id',
    ): Builder;
}

authorize() is per-record (called from FormRequest::authorize() for store/update and from controllers for show/destroy). scope() constrains index queries.

The default binding is AllowAllAuthorizer (everything goes through). Hosts that want strict tenancy bind their own implementation.

Installation

composer require whilesmart/eloquent-owner-access

The service provider auto-registers via Laravel package discovery and bindIfs the default authorizer.

Usage in a package

In a StoreXRequest:

use Whilesmart\OwnerAccess\Concerns\AuthorizesOwnerRequest;

class StoreAccountRequest extends FormRequest
{
    use AuthorizesOwnerRequest;

    public function authorize(): bool
    {
        return $this->authorizeOwnerInRequest();
    }

    public function rules(): array { /* ... */ }
}

In an UpdateXRequest:

public function authorize(): bool
{
    return $this->authorizeOwnerOfBoundModel('account');
}

In the controller:

use Whilesmart\OwnerAccess\Concerns\AuthorizesOwnerController;

class AccountController extends Controller
{
    use AuthorizesOwnerController;

    public function index(Request $request)
    {
        $query = Account::query();
        $query = $this->scopeAccessibleOwners($query, $request->user());
        // ...
    }

    public function show(Account $account, Request $request)
    {
        $this->authorizeAccessTo($account, $request->user());
        // ...
    }

    public function destroy(Account $account, Request $request)
    {
        $this->authorizeAccessTo($account, $request->user());
        $account->delete();
        // ...
    }
}

Update is already covered by UpdateXRequest::authorize(), so no controller change is needed there.

Usage in a host app

By default everything is allowed. To enforce tenancy, write an authorizer that consults your tenancy model and bind it:

namespace App\Authorization;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Whilesmart\OwnerAccess\Contracts\OwnerAuthorizer;

class WorkspaceMemberAuthorizer implements OwnerAuthorizer
{
    public function authorize(?Authenticatable $user, string $ownerType, mixed $ownerId): bool
    {
        if ($user === null || $ownerType !== \App\Models\Workspace::class) {
            return false;
        }

        return $user->workspaces()->whereKey($ownerId)->exists();
    }

    public function scope(Builder $query, ?Authenticatable $user, string $ownerTypeColumn = 'owner_type', string $ownerIdColumn = 'owner_id'): Builder
    {
        if ($user === null) {
            return $query->whereRaw('0 = 1');
        }

        return $query->where($ownerTypeColumn, \App\Models\Workspace::class)
            ->whereIn($ownerIdColumn, $user->workspaces()->pluck('workspaces.id'));
    }
}

Then in AppServiceProvider::register():

$this->app->bind(
    \Whilesmart\OwnerAccess\Contracts\OwnerAuthorizer::class,
    \App\Authorization\WorkspaceMemberAuthorizer::class,
);

That single binding switches every consuming package over to strict mode at once.

Backwards compatibility

The provider uses bindIf, so any explicit binding the host registers (before or after) wins. Without an explicit binding, the default AllowAllAuthorizer keeps current behavior.

Testing

composer test

License

MIT.

Related Packages

gabrieloliverio/laravel5-generators

Database metadata-based generators for Laravel 5

1
erirk/paypalpayment

laravel-paypalpayment is simple package help you process direct credit card paym...

0
doctrine/dbal

Powerful PHP database abstraction layer (DBAL) with many features for database s...

631,580,251 9,707
laravel/framework

The Laravel Framework.

580,311,802 34,925
laravel/tinker

Powerful REPL for the Laravel framework.

489,754,436 7,444