Skip to content
LaravelBlog

Livewire 4.x Pull Request Changelog: PR #10510 - Fix #[Authorize] Attribute to Trigger Component exception() Hook

LaravelBlogBot
LaravelBlogBot

Contributed by ghabriel25

Part of the livewire v4.4.7 release

Livewire PR #10510 fixes a subtle but important gap in the #[Authorize] attribute on the 4.x branch: authorization failures thrown before your action runs never reached the component's exception() hook. The fix routes attribute-based authorization through the same exception-handling wrapper as normal method calls, so your custom error handling now behaves consistently. The PR also adds two regression tests and a small reflection cleanup. If you're upgrading from v3.8.10 into the 4.x line, you get this behavior out of the box — no action required, though a few workarounds you may have written elsewhere can now be deleted.

The bug: authorization exceptions skipped the exception() hook

The #[Authorize] attribute performs a gate or policy check before your action method executes. Internally, that check runs through SupportAttributes::call(), which fires ahead of the method call and outside the wrapper pipeline that powers component exception hooks. When authorization failed, the resulting AuthorizationException was thrown before Livewire could hand it to your component.

In practice, code like this never reached the hook:

class PostPolicy
{
    public function edit(User $user, Post $post): bool
    {
        return false;
    }
}
use Illuminate\Auth\Access\AuthorizationException;
use Livewire\Attributes\Authorize;
use Livewire\Component;

class PostEditor extends Component
{
    #[Authorize('edit', 'post')]
    public function save(Post $post)
    {
        // Only reached when authorized.
    }

    public function exception($e, $stopPropagation)
    {
        if ($e instanceof AuthorizationException) {
            $stopPropagation();

            $this->dispatch('notify', message: 'You cannot edit this post.');
        }
    }
}

The exception() method was simply never invoked; the request fell through to the default forbidden handling instead. That inconsistency is what issue #10410 reported.

The fix: authorize inside the wrapper

Rather than restructure the attribute system, BaseAuthorize::call() now wraps the component and runs the authorization check inside a tap() callback — the same technique BaseValidate already uses:

use function Livewire\wrap;

public function call(array $parameters): void
{
    wrap($this->component)->tap(function () use ($parameters) {
        // Gate/policy check; may throw.
    });
}

Because the check now executes inside wrap(), anything it throws flows through Livewire's Wrapper class — the exact place where the exception() hook is invoked. This approach replaces the simpler patch attempted in PR #10483 with one that matches how the rest of the framework handles attribute-time exceptions.

Tests and cleanup included

Two regression tests lock in the behavior:

  • test_authorize_attribute_trigger_component_exception_hook asserts the hook fires on a failed attribute check, and that calling $stopPropagation() lets the request complete OK instead of forbidden.
  • test_can_authorize_using_attribute_and_trait_method_at_the_same_time confirms you can still mix #[Authorize] with $this->authorize() calls inside the method body, and that the hook stays silent when authorization passes.

The diff also swaps ReflectionObject for ReflectionMethod when matching named arguments (like 'post') to method parameters. Reflecting on the method directly has fewer layers than walking the whole object, and resolveArgument() drops its now-redundant $parameters argument since the lazy dependency-resolution closure already captures it. Both are internal details with no public-facing effect.

Upgrade impact

There is no API change: #[Authorize('ability', $argument)] works exactly as documented. The change is behavioral, in one direction:

  1. If you never noticed the bug, there is nothing to do — hooks now fire as you'd expect.
  2. If you worked around it — for example, calling $this->authorize() manually inside actions so exceptions hit the hook, or handling AuthorizationException in a global handler — you can move that logic into exception() and simplify.
  3. If you relied on the hook not firing for attribute-based failures (unlikely, but possible if a component has an exception() hook and you wanted the default 403 response), review that component: intercepting in the hook now takes precedence, just as it does for every other exception.

For teams coming from v3.8.10, the #[Authorize] attribute itself is part of what you're adopting in 4.x; this fix means its error handling is consistent from day one.

Takeaways

  • #[Authorize] failures now trigger the component's exception() hook, consistent with all other exceptions.
  • Implemented by wrapping authorization in wrap($component)->tap(...), the same pattern validation attributes use.
  • No public API changes; internal reflection streamlined (ReflectionMethod over ReflectionObject).
  • Regression tests cover both the hook firing and attribute/trait authorization coexisting.

Sources

More from this release

Related Articles