I was investigating how Livewire handles authorization for #[Json] methods and found an interesting execution-order problem.

The issue appears when an application relies on a global ComponentHook::call() to authorize component actions.

In the tested Livewire 4.4.8 implementation, a method marked with #[Json] can execute before the global component authorization hook gets a chance to reject the call.

The important part is that the method can perform its database operation even though Livewire eventually returns HTTP 403.

The problem

Livewire uses separate listeners for JSON methods and global component hooks.

The relevant execution path is:

  1. SupportJson receives the component call.
  2. It identifies the method marked with #[Json].
  3. It checks the built-in method-level authorization attributes.
  4. It executes the JSON method directly.
  5. The global ComponentHookRegistry listener runs afterward.
  6. The custom authorization hook rejects the request.

At that point, the method body has already executed.

Relevant source:

Database reproduction

I tested this using a Laravel application with two authenticated users and a separate SQLite database.

User 1 was a normal member.

User 2 was an administrator and owner of the target database record.

The member attempted to modify the administrator-owned record through a #[Json] method. The application relied on the global component hook to reject unauthorized actions.

The result:

authenticated_session_user_id=1 target_record_owner_user_id=2 http_response_status=403 database_value=modified-by-member database_changed_by_user_id=1 CROSS-USER DATABASE PROOF PASSED 

The database was actually modified by user 1.

Control tests

I tested other execution paths to compare the behavior.

Test HTTP Database
Non-admin JSON action 403 Modified
Non-admin ordinary action 403 Unchanged
JSON with inline Gate authorization 403 Unchanged
JSON with built-in #[Authorize] 403 Unchanged
Administrator JSON action 200 Modified
JSON request without CSRF token 419 Unchanged

The inline Gate::authorize() check and built-in #[Authorize] attribute prevented the unauthorized database modification.

This makes the execution-order difference particularly interesting.

Environment

The original reproduction was performed with:

  • PHP 8.4.24
  • Laravel Framework 13.42.0
  • Livewire 4.4.8
  • SQLite

The test used authenticated sessions and Laravel's HTTP request handling.

Impact

Applications that rely exclusively on global ComponentHook::call() authorization for #[Json] methods can allow unauthorized application-side operations.

The database test demonstrates cross-user record modification.

This is an authorization problem, not a general bypass of Laravel's authorization system. Authorization performed inside the JSON method before the sensitive operation prevented the database write in my tests.

Reproduction and evidence

I published the research report and test material on GitHub.

Technical report:

https://github.com/djokage/security-research/blob/main/reports/livewire-global-hook-authorization-order.md

Standalone reproduction:

https://github.com/djokage/security-research/tree/main/reproducers/livewire-global-hook-json-order

Recorded database evidence:

https://github.com/djokage/security-research/blob/main/reports/evidence/livewire-cross-user-db-proof.txt

Final note

I am interested in independent reproduction and technical discussion.

I would particularly like to hear from developers who use global Livewire component hooks for application-wide authorization.

The important question is whether the global authorization hook should be able to reject a JSON action before the method body is executed.