12 KiB
Livewire Method-Level Authorization Security
Date: 2026-01-03 Security Feature: Protection Against Livewire Direct Method Invocation Attacks Severity: Critical
Overview
This document details the comprehensive method-level authorization protection implemented across all admin management Livewire components to prevent unauthorized direct method invocation attacks.
Vulnerability Background
The Livewire Security Gap
Livewire components have a critical security consideration: the mount() method only runs once when a component is loaded. After that, any public method can be called directly via browser console using:
Livewire.find('component-id').call('methodName', parameters)
Problem: If authorization is only checked in mount(), an attacker can:
- Access a protected page (passing the mount() check)
- Call sensitive methods directly via browser console
- Bypass all authorization checks that only exist in mount()
Attack Scenario Example
// VULNERABLE CODE
class Manage extends Component
{
public function mount()
{
// Only checks authorization on initial load
if (!auth()->user()->isAdmin()) {
abort(403);
}
}
public function deleteProfile($id)
{
// NO authorization check here!
Profile::find($id)->delete();
}
}
Attack:
// Attacker loads the page (passes mount check)
// Then directly calls the method:
Livewire.find('profile-manage').call('deleteProfile', 123)
// Profile deleted without authorization!
Solution: RequiresAdminAuthorization Trait
Implementation
File: app/Http/Livewire/Traits/RequiresAdminAuthorization.php
<?php
namespace App\Http\Livewire\Traits;
use App\Helpers\ProfileAuthorizationHelper;
trait RequiresAdminAuthorization
{
private $adminAuthorizationChecked = null;
protected function authorizeAdminAccess(bool $forceRecheck = false): void
{
// Use cached result unless force recheck
if (!$forceRecheck && $this->adminAuthorizationChecked === true) {
return;
}
$activeProfileType = session('activeProfileType');
$activeProfileId = session('activeProfileId');
if (!$activeProfileType || !$activeProfileId) {
abort(403, __('No active profile selected'));
}
$profile = $activeProfileType::find($activeProfileId);
if (!$profile) {
abort(403, __('Active profile not found'));
}
// Validate profile ownership using ProfileAuthorizationHelper
ProfileAuthorizationHelper::authorize($profile);
// Verify admin or central bank permissions
if ($profile instanceof \App\Models\Admin) {
$this->adminAuthorizationChecked = true;
return;
}
if ($profile instanceof \App\Models\Bank) {
if ($profile->level === 0) {
$this->adminAuthorizationChecked = true;
return;
}
abort(403, __('Central bank access required'));
}
abort(403, __('Admin or central bank access required'));
}
}
Security Features
- ProfileAuthorizationHelper Integration: Uses centralized authorization helper
- Cross-Guard Attack Prevention: Validates that authenticated guard matches profile type
- Database-Level Validation: Verifies profile exists and user owns it
- Bank Level Validation: Only central bank (level=0) can access admin functions
- Performance Caching: Caches authorization result within same request
- IDOR Prevention: Prevents unauthorized access to other users' profiles
Protected Components
Complete Coverage (27 Methods Across 6 Components)
1. Posts/Manage.php (7 methods)
edit()- Edits post translationcreate()- Creates new postsave()- Saves post datadeleteSelected()- Bulk deletes postsundeleteSelected()- Bulk undeletes postsstopPublication()- Stops post publicationstartPublication()- Starts post publication
2. Categories/Manage.php (4 methods)
deleteSelected()- Bulk deletes categoriesdeleteCategory()- Deletes single categoryupdateCategory()- Updates categorystoreCategory()- Creates new category
Additional Components:
Categories/Create.php- No methods to protect (view only)Categories/ColorPicker.php- No methods to protect (UI only)
3. Tags/Manage.php (3 methods)
deleteTag()- Deletes single tagdeleteSelected()- Bulk deletes tagsupdateTag()- Updates tag
4. Tags/Create.php (1 method)
create()- Creates new tag
5. Profiles/Manage.php (5 methods)
updateProfile()- Updates profile dataattachProfile()- Attaches profile to another profiledeleteProfile()- Deletes profilerestoreProfile()- Restores deleted profiledeleteSelected()- Bulk deletes profiles
6. Profiles/Create.php (1 method)
create()- Creates new profile (Critical fix - was unprotected)
Additional Components:
Profiles/ProfileTypesDropdown.php- No methods to protect (UI only)
7. Mailings/Manage.php (6 methods)
saveMailing()- Saves/creates mailingdeleteMailing()- Deletes single mailingsendMailing()- Sends mailing to recipientsbulkDeleteMailings()- Bulk deletes mailings (Critical fix - was unprotected)sendTestMail()- Sends test emailsendTestMailToSelected()- Sends test email to selected recipients
Additional Components:
Mailings/LocationFilter.php- No methods to protect (UI only)
Critical Vulnerabilities Fixed
1. Profiles/Create.php - Unauthorized Profile Creation
Severity: Critical
Method: create()
Risk: Allowed unauthorized users to create any profile type (User, Organization, Bank, Admin)
Status: ✅ Fixed (line 391)
2. Mailings/Manage.php - Unauthorized Bulk Deletion
Severity: High
Method: bulkDeleteMailings()
Risk: Allowed unauthorized bulk deletion of mailings
Status: ✅ Fixed (line 620)
Methods That Don't Need Protection
The following method types are intentionally left without authorization checks:
- Livewire Lifecycle Methods:
mount(),render(),updated*(),updating*() - Computed Properties:
get*Property()methods (read-only) - UI Helpers: Modal openers/closers, sorting, searching, filtering
- Event Listeners: Methods that only emit events or update UI state
Rationale: Even if users call these methods directly, they cannot execute dangerous operations without going through the protected methods.
Usage Pattern
Adding the Trait
<?php
namespace App\Http\Livewire\YourComponent;
use App\Http\Livewire\Traits\RequiresAdminAuthorization;
use Livewire\Component;
class YourComponent extends Component
{
use RequiresAdminAuthorization;
// ... component code
}
Protecting a Method
public function deleteItem($itemId)
{
// CRITICAL: Authorize admin access at the start of the method
$this->authorizeAdminAccess();
// Now safe to perform the sensitive operation
Item::find($itemId)->delete();
$this->notification()->success(__('Item deleted'));
}
Force Recheck (Optional)
For methods that need fresh authorization (e.g., after significant time has passed):
public function criticalOperation()
{
// Force fresh authorization check
$this->authorizeAdminAccess(forceRecheck: true);
// ... perform operation
}
Testing Requirements
All protected methods should have corresponding tests that verify:
- Unauthorized Access Blocked: Non-admin users cannot call the method
- Cross-Guard Attacks Blocked: Users authenticated to wrong guard cannot access
- IDOR Prevention: Users cannot access other users' data
- Authorization Success: Authorized admin users can successfully call the method
Example Test Structure
public function test_non_admin_cannot_delete_profile()
{
$user = User::factory()->create();
$this->actingAs($user);
$component = Livewire::test(Manage::class);
$component->call('deleteProfile', 1)
->assertStatus(403);
}
public function test_admin_can_delete_profile()
{
$admin = Admin::factory()->create();
$this->actingAs($admin, 'admin');
session(['activeProfileType' => Admin::class, 'activeProfileId' => $admin->id]);
$profile = User::factory()->create();
$component = Livewire::test(Manage::class);
$component->call('deleteProfile', $profile->id)
->assertHasNoErrors();
$this->assertSoftDeleted('users', ['id' => $profile->id]);
}
Performance Considerations
Caching Mechanism
The trait implements request-scoped caching to avoid repeated authorization checks:
private $adminAuthorizationChecked = null;
protected function authorizeAdminAccess(bool $forceRecheck = false): void
{
// Return cached result if already checked
if (!$forceRecheck && $this->adminAuthorizationChecked === true) {
return;
}
// ... perform authorization
// Cache the result
$this->adminAuthorizationChecked = true;
}
Benefits:
- Multiple method calls in same request only check once
- No database overhead for repeated checks
- Optional force recheck for critical operations
Migration Guide
For Existing Components
- Add the trait:
use RequiresAdminAuthorization; - Identify sensitive methods: Any method that modifies data
- Add authorization call:
$this->authorizeAdminAccess();as first line - Test thoroughly: Verify both authorized and unauthorized access
For New Components
- Include trait from start: Add
RequiresAdminAuthorizationtrait - Protect all data-modifying methods: Add authorization to create, update, delete operations
- Document method protection: Add comment indicating critical authorization
- Write tests: Include authorization tests from the beginning
Security Best Practices
1. Always Protect Data-Modifying Methods
// ✅ CORRECT
public function updateData($id, $value)
{
$this->authorizeAdminAccess();
Data::find($id)->update(['value' => $value]);
}
// ❌ WRONG
public function updateData($id, $value)
{
// Missing authorization check!
Data::find($id)->update(['value' => $value]);
}
2. UI Methods Don't Need Protection
// ✅ CORRECT - No protection needed for UI helper
public function openEditModal($id)
{
$this->editId = $id;
$this->showEditModal = true;
}
// ✅ CORRECT - Actual update is protected
public function saveEdit()
{
$this->authorizeAdminAccess(); // Protection here!
Data::find($this->editId)->update($this->editData);
}
3. Document Protected Methods
public function deleteItem($id)
{
// CRITICAL: Authorize admin access for deletion
$this->authorizeAdminAccess();
Item::find($id)->delete();
}
Monitoring & Audit
Failed Authorization Logging
The ProfileAuthorizationHelper logs all failed authorization attempts with:
- User ID attempting access
- Profile they tried to access
- Timestamp and IP address
- Stack trace for debugging
Success Indicators
- No authorization errors in logs
- Users report proper access control
- Security tests pass consistently
Related Documentation
references/SECURITY_OVERVIEW.md- Overall security architectureapp/Helpers/ProfileAuthorizationHelper.php- Centralized authorization helperreferences/ADMIN_MANAGEMENT_SECURITY_FIXES_2025-12-31.md- Previous security fixes
Summary
This comprehensive method-level authorization protection ensures that all sensitive admin operations require proper authorization on every method call, not just on component mount. This prevents Livewire direct method invocation attacks and provides defense-in-depth security for the admin management system.
Total Protected Methods: 27 Components Secured: 6 main management components Critical Vulnerabilities Fixed: 2 Security Level: Enterprise-grade protection against IDOR, cross-guard attacks, and direct method invocation