livewire/livewire: Fix: Base attributes now fire on event-dispatched method calls
Contributed by ghabriel25
Part of the livewire v4.4.7 release
Pull request #10384 fixes a subtle but confusing bug on Livewire's 4.x branch: the internal BaseAuthorize and BaseRenderless attributes were silently ignored whenever the method they decorated was invoked as an event listener. The root cause came down to a single predicate in SupportEvents::call — is_subclass_of, which matches subclasses of a class but not the class itself. Swapping it for instanceof makes the detection correct in both cases. If you're upgrading from v3.8.10, this is a low-risk fix with no public API changes; its value is making the framework's internal authorization and renderless machinery behave consistently, which matters most to contributors working with Livewire's own test suite.
Categories: Fix, Security
What changed
The fix is two one-line edits in src/Features/SupportEvents/SupportEvents.php. When a component method is invoked as an event listener (via #[On] or the internal #[BaseOn]), its normal attribute "call" hook doesn't run — so SupportEvents::call re-applies method-level authorize and renderless attributes manually. Those manual checks were the buggy part:
// Before
->filter(fn ($i) => is_subclass_of($i, BaseAuthorize::class))
// After
->filter(fn ($i) => $i instanceof BaseAuthorize)
The identical change was applied to the BaseRenderless check just below it. The PR also adds regression tests to SupportAuthorization/UnitTest.php and SupportRenderless/BrowserTest.php, and closes issue #10383.
Why is_subclass_of missed the base attributes
is_subclass_of($attribute, BaseAuthorize::class) answers the question "is this attribute's class a descendant of BaseAuthorize?" It returns false when the attribute instance is literally BaseAuthorize itself. The public-facing #[Authorize] and #[Renderless] attributes subclass their Base* counterparts, so they sailed through the filter. The base attributes — which PR #10346 established as strictly internal — did not.
The result: whenever a method carrying #[BaseAuthorize] or #[BaseRenderless] was called through the event system, both attributes fell through the filter and did nothing. No authorization check ran, and no renderless behavior was applied. Direct method calls were unaffected; only the event-listener path skipped the checks, which is exactly the kind of inconsistency that produces maddening test failures.
The bug in practice
The new regression tests show the scenario concretely. An unauthorized user dispatches an event that triggers a method guarded by #[BaseAuthorize]:
Livewire::actingAs(AuthorizationUser::find(2)) // not authorized
->test(new class extends TestComponent {
#[BaseAuthorize(AuthorizationPost::class)]
#[BaseOn('some-event')]
public function store(): bool
{
return true;
}
})
->dispatch('some-event')
->assertForbidden(); // failed before the fix — the action ran anyway
Before this PR, assertForbidden() would fail because the authorize attribute never fired on the event path. The same applies to renderless behavior: the new browser test attaches #[BaseRenderless] and #[BaseOn('some-event')] to a method triggered by wire:click="$dispatch('some-event')" and asserts that render() is not called (the render counter stays at 1). Previously, the component re-rendered as if the attribute weren't there — because, as far as the event system was concerned, it wasn't.
Who is affected
- Framework contributors writing unit or browser tests against
BaseAuthorizeandBaseRenderlessare the primary audience. Tests combining these attributes with#[BaseOn]now behave the same as tests using->call()directly. - Internal Livewire code paths that attach the base attributes and rely on event dispatch are now properly guarded.
- Regular application code using the public
#[Authorize],#[Renderless], and#[On]attributes was not broken by this bug, since those subclass the base attributes and matchedis_subclass_ofjust fine.
One behavioral note worth flagging: if anything relied on base attributes being ignored on the event path — deliberately or otherwise — those checks now genuinely run. An unauthorized event dispatch will return a 403 instead of executing the action, and renderless methods triggered by events will no longer trigger a re-render.
Regression coverage and upgrade notes
The PR adds four pieces of regression protection: two unit tests confirming that an authorized user gets a 200 (assertOk) and an unauthorized user gets a 403 (assertForbidden) when the guarded method runs via dispatch(), plus a browser test verifying renderless behavior on the event path. These lock the instanceof semantics in place so the predicate can't quietly regress.
For teams upgrading from v3.8.10, there is nothing to migrate: no config changes, no API changes, no template updates. The only guidance is the one that already existed — in application code, stick to the public attributes (#[Authorize], #[Renderless], #[On]) and treat the Base* attributes as internal framework plumbing. After upgrading, expect stricter, more correct behavior: event-triggered methods guarded by these attributes will now enforce authorization and renderless semantics every time, not just when called directly.
Takeaways
SupportEvents::callnow usesinstanceofinstead ofis_subclass_ofto detectBaseAuthorizeandBaseRenderless, so both fire when a method is invoked as an event listener.- Previously, base attributes used directly were silently skipped on the event path — no auth check, no renderless behavior.
- Public attributes (
#[Authorize],#[Renderless]) were unaffected; the bug mainly hit internal code and contributor test suites. - If you were (against guidance) using
Base*attributes directly, expect them to start enforcing behavior after this fix. - New unit and browser regression tests cover both the authorized and unauthorized event-dispatch scenarios, plus renderless rendering behavior.
- No upgrade steps are required when moving from v3.8.10; this fix ships on the 4.x line.