When Livewire Returns HTTP 403 but the Database Has Already Changed
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 co
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:
-
SupportJsonreceives the component call. - It identifies the method marked with
#[Json]. - It checks the built-in method-level authorization attributes.
- It executes the JSON method directly.
- The global
ComponentHookRegistrylistener runs afterward. - The custom authorization hook rejects the request.
At that point, the method body has already executed.
Relevant source:
- https://github.com/livewire/livewire/blob/v4.4.8/src/Features/SupportJson/SupportJson.php
- https://github.com/livewire/livewire/blob/v4.4.8/src/ComponentHookRegistry.php
- https://github.com/livewire/livewire/blob/v4.4.8/src/EventBus.php
- https://github.com/livewire/livewire/blob/v4.4.8/src/Mechanisms/HandleComponents/HandleComponents.php
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:
Standalone reproduction:
https://github.com/djokage/security-research/tree/main/reproducers/livewire-global-hook-json-order
Recorded database evidence:
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.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.