Livewire feels like magic because it bridges the gap between backend and frontend. But this convenience comes with a hidden risk: Every public property and method in your component is a public API endpoint.
If you are not careful, a malicious user can inspect the network payload, modify state you thought was "private", or execute actions they shouldn't have access to.
In this guide, we are going to look at three common vulnerabilities in TALL stack applications and how to fix them using modern Livewire 3 features and Beartropy best practices.
🔓 Vulnerability 1: The "Hidden Button" Fallacy
We often use Blade directives to hide actions from unauthorized users.
1
2@if(Auth::user()->isAdmin())
3 <x-bt-button wire:click="deleteUser({{ $user->id }})" color="danger">
4 Delete User
5 </x-bt-button>
6@endif
1// ❌ INSECURE COMPONENT
2class UserTable extends Component
3{
4 public function deleteUser(User $user)
5 {
6 // If I open the browser console and type Livewire.find('...').deleteUser(5),
7 // this will execute even if I am not an admin!
8 $user->delete();
9 }
10}
The Fix: Explicit Authorization
Never rely on the UI for security. Always authorize the action on the server.
1// ✅ SECURE COMPONENT
2class UserTable extends Component
3{
4 public function deleteUser(User $user)
5 {
6 // Method 1: Laravel Policy Gate
7 $this->authorize('delete', $user);
8
9 $user->delete();
10 }
11}
🕵️ Vulnerability 2: Sensitive Data Leaks
A common mistake is storing entire Eloquent models in public properties when you only need a few fields.
1// ❌ INSECURE
2class UserProfile extends Component
3{
4 public User $user;
5}
When Livewire dehydrates this component, it sends every single column of the User model to the frontend in the JSON payload. If your user table has is_admin, stripe_id, or api_token columns, they are now visible in the browser's "View Source" or Network tab.
The Fix: DTOs or Specific Properties
Only expose what the UI needs to render.
1// ✅ SECURE
2class UserProfile extends Component
3{
4 public string $name;
5 public string $email;
6
7 public function mount(User $user)
8 {
9 $this->name = $user->name;
10 $this->email = $user->email;
11 }
12}
🔒 Vulnerability 3: ID Tampering
Imagine a component that updates a user's ID card. You might store the ID in a public property.
1class UpdateProfile extends Component
2{
3 public $userId;
4 public $name;
5
6 public function save()
7 {
8 $user = User::find($this->userId);
9 $user->update(['name' => $this->name]);
10 }
11}
A user can use browser dev tools to change $userId from 5 to 6 and update someone else's profile.
The Fix: The #[Locked] Attribute
Livewire 3 introduced a brilliant attribute that prevents the frontend from modifying specific properties.
1use Livewire\Attributes\Locked;
2
3class UpdateProfile extends Component
4{
5 #[Locked] // 🛡️ This property cannot be tampered with from the client
6 public $userId;
7
8 public $name;
9
10 public function save()
11 {
12 $user = User::find($this->userId);
13 $user->update(['name' => $this->name]);
14 }
15}
🛡️ Best Practices Checklist
When building with Beartropy and Livewire, keep this checklist on your desk:
- Authorize Everything: Every method triggered by
wire:click must have an authorization check.
- Lock IDs: Use
#[Locked] for any ID or state that shouldn't change.
- Validate Inputs: Never trust
$this->property without running $this->validate() first.
- Middleware: Ensure your routes are protected by
auth and verified middleware.
Security isn't a feature; it's the foundation. Build safe apps.