Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

paaaaayns's avatar

How to Use the Correct Policy When Authorizing an Action with Related Models

I'm trying to implement authorization using policies, but I've hit a bit of a roadblock.

I have two models: Property and Lease. A Lease belongs to a Property, and in order to create a new lease, the authenticated user must be either the creator (creator_id) or manager (manager_id) of the associated Property.

In my LeaseController, I have this logic:

public function store(StoreLeaseRequest $request) {
        $validated = $request->validated();

        $property = Property::find($validated['property_id']);

        if (Gate::denies('create', $property)) {
            return response()->json([
                'success' => false,
                'message' => 'You do not have permission to create a lease for this property.',
            ], 403);
        }

        return response()->json([
            'success' => true,
            'message' => 'Lease created successfully.',
        ], 201);
    }

However, this doesn't work as intended because Laravel automatically uses the policy associated with the model passed to Gate::denies(). In this case, it's calling the create() method from PropertyPolicy — but I actually need it to call the create() method from my LeasePolicy.

This is what my LeasePolicy.php looks like

public function create(User $user, Property $property): bool
    {
        Log::info('LeasePolicy@create: ', [
            'user_id' => $user->id,
            'property_id' => $property->id,
        ]);

        return true;
    }

I know this would be doable with Gates where I could define a custom rule manually, but I'm really trying to stick with policies only in order to stay organized and consistent.

Is it possible to do this using Policies?

Any insights would be appreciated!

Thanks in advance.

0 likes
3 replies
hellolara's avatar

I believe you have to explicitly tell it which policy to use in this case, otherwise it will default to whatever class $property is

if (Gate::denies([LeasePolicy::class, 'create'], $property)) {
//
}
paaaaayns's avatar
paaaaayns
OP
Best Answer
Level 2

@hellolara I actually tried this, but it didn’t work for me — the log I placed inside the LeasePolicy@create method wasn’t triggered at all.

However, I found a workaround that follows a similar idea. Instead of relying on the Gate facade to resolve the policy method, I resolved the policy class directly using the service container:

$policy = app(LeasePolicy::class);

if (! $policy->create($user, $property)) {
    return response()->json([
        'success' => false,
        'message' => 'You do not have permission to create a lease for this property.',
    ], 403);
}

This way, I can call the method explicitly and pass in the arguments as needed. It works as expected and the policy logic is properly executed.

krisi_gjika's avatar

If your Lease creation authorization is dependent on the related Property, your Policy class should reflect that

public function store(StoreLeaseRequest $request) {
        $validated = $request->validated();

        $property = Property::find($validated['property_id']);

        // in this case the first arg determines the method, second arg the policy,
        // and the rest are passed as arguments to the policy method
        $request->user()->can('create', [Lease::class, $property]);

...

class LeasePolicy {

    public function create(User $user, Property $property): bool
    {
        return $property->manager->is($user) || $property->owner->is($user);
    }

Please or to participate in this conversation.