Laravel Livewire : l'importance de sécuriser l'ID de son model
Livewire est un framework pour Laravel qui permet de créer des interfaces dynamiques depuis le backend. Attention tout de même à la sécurité.

Livewire est vraiment un outil génial, il permet à du code en backend de créer un frontend réactif sans avoir besoin d’écrire la moindre ligne de Javascript.
Super non ?
Malheureusement il y a un revers à la médaille : la sécurité du composant publique.
Pourquoi sécuriser l’ID de son model
Pour faire simple, Livewire utilise des propriétés publiques pour effectuer ses interactions avec le frontend. Dans l’absolu ce n’est pas un problème sauf si vous savez comment utiliser ce comportement pour casser un composant.
Une des bonnes pratiques à mettre en place quand vous utilisez Livewire est de ne rendre publique que l’ID de votre model et pas l’ensemble de ses propriétés. Cela peut paraitre plus pratique au premier coup d’oeil mais cette manière de faire va impacter grandement les performances de votre composant si votre model possède un grand nombre de propriétés. Je ne vais pas aborder cette notion dans cet article car ce n’est pas son objectif.
Je vais commencer ma démonstration en créant un composant et vous montrer ensuite comment le casser.
Créer un composant de test
Ce composant de test va permettre de créer un brouillon d’article. Il est volontairement simplifié au maximum et je ne détaille pas la vue frontend qui ne présente pas d’intêret pour cette démonstration.
namespace App\Http\Livewire; use App\Models\Post;use Livewire\Component;use Illuminate\Contracts\View\View;use Illuminate\Auth\Access\AuthorizationException;use Illuminate\Foundation\Auth\Access\AuthorizesRequests; class EditPostComponent extends Component{ use AuthorizesRequests; public int $postId; public $state = [ 'title' => '', 'body' => '', 'published' => false, ]; public function rules(): array { return [ 'state.title' => 'required|string|max:255', 'state.body' => 'required|string|max:5000', 'state.published' => 'boolean', ]; } /** * @throws AuthorizationException */ public function mount(Post $post): void { $this->authorize('update', $post); $this->postId = $post->id; $this->state = $post->only('title', 'body', 'published'); } public function render(): View { return view('livewire.post.edit-form'); } /** * @throws AuthorizationException */ public function save(): void { $this->validate(); $post = Post::findOrFail($this->postId); $this->authorize('update', $post); $post->title = $this->state['title']; $post->body = $this->state['body']; $post->save(); }}
Casser un composant : démonstration par l’exemple
Plusieurs options s’offrent à vous pour modifier une propriété publique dans un composant Livewire. Ici je vais utiliser la plus simple : la console développeur de Google Chrome.
Je cherche dans le code source un wire:id=XXX et je le sélectionne. Ensuite je vais dans la console et je saisie $0.__livewire.data : je vais récupérer toutes les propriétés publiques du composant. Si je saisie $0.__livewire.$wire.postId = 47 dans la console : je viens de modifier la valeur de $postId dans le composant. Maintenant si je modifie mon formulaire en frontend les modifications seront effectuées pour le Post avec l’ID 47.
Ça craint non ?
Comment sécuriser un composant
Il existe autant de méthodes pour sécuriser un composant que de développeurs.
Une première serait d’attendre la sortie de la v3 de Livewire qui introduira la possibilité de protéger une propriété publique via l’annotation / @locked /.
Une approche que j’apprécie pas mal c’est d’écouter au sein même du composant si une modification sur la propriété $postId est effectuée et renvoyer une erreur lorsque cela arrive.
Un Trait pour les sécuriser
Comme je n’aime pas me répéter (j’applique la méthode DRY - pour la découvrir je vous dirige vers le blog de l’excellent Code-Garage), j’ai créé un Trait pour pouvoir utiliser cette approche facilement dans mes composants.
Je vais aussi utiliser l’annotation / @locked / pour être prêt pour Livewire v3. Une fois la mise à jour effectuée il suffira d’enlever le Trait et voila !
Pour sécuriser le composant de test, il faut lui ajouter le Trait et l’annotation :
namespace App\Http\Livewire; use App\Models\Post; [...] class EditPostComponent extends Component {- use AuthorizesRequests; + use AuthorizesRequests, WithLockedPublicPropertiesTrait; - public int $postId; + /** @locked */+ public int $postId;
Et le contenu du Trait :
namespace App\Traits\Livewire; use Str;use ReflectionException;use App\Exceptions\LockedPublicPropertyTamperException; trait WithLockedPublicPropertiesTrait{ /** * @throws LockedPublicPropertyTamperException|ReflectionException */ public function updatingWithLockedPublicPropertiesTrait($name): void { $propertyName = Str::of($name)->explode('.')->first(); $reflectionProperty = new \ReflectionProperty($this, $propertyName); if (Str::of($reflectionProperty->getDocComment())->contains('@locked')) { throw new LockedPublicPropertyTamperException("Vous n'êtes pas autorisé à jouer avec la propriété protégée {$propertyName}"); } }}
Ce Trait utilise ReflectionProperty pour vérifier si @locked est disponible sur la propriété qui vient d’être modifiée. Lorsque cela arrive une exception de type LockedPublicPropertyTamperException est envoyée.
Vous devez créer cette exception au préalable pour que ça fonctionne.
Conclusion
Je viens de vous présenter une manière de sécuriser un composant Livewire pour éviter qu’un petit malin ne modifie tout et n’importe quoi sur votre application.
Il est certainement possible de faire mieux mais gardez à l’esprit qu’il faut toujours sécuriser les données qui sont accessibles aux utilisateurs finaux.