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é.

Laravel Livewire : l'importance de sécuriser l'ID de son model

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.

Contenus associés

Laravel : créer facilement un sitemap

Je vous présente un paquet qui vous permet de générer très facilement un sitemap pour votre site sous Laravel.

Laravel : créer facilement un sitemap
Fonctionnement du blog : pourquoi Laravel et WordPress

J’explique dans cet article pourquoi j’ai choisi Laravel et WordPress comme stack de départ pour le fonctionnement de ce blog.

Fonctionnement du blog : pourquoi Laravel et WordPress
Laravel Artisan : make - le guide complet

Le framework Laravel dispose d'un ensemble complet de commandes, la plus utilisée étant certainement make. Connaissez-vous toutes les commandes disponibles ?

Laravel Artisan : make - le guide complet
Forcer la redirection HTTP vers HTTPS sur Laravel 8, 9 et plus

Votre hébergeur ne propose pas le HTTPS par défaut, vous ne pouvez pas modifier le htaccess… Comment faire ? J’ai LA réponse à vos questions.

Forcer la redirection HTTP vers HTTPS sur Laravel 8, 9 et plus
Hébergez vos polices Google sans effort avec Laravel

Pensez à héberger vos polices Google sur votre serveur : vous gagnerez en vitesse de chargement et vous serez en règle avec le RGPD. Je vous donne ma méthode dans cet article.

Hébergez vos polices Google sans effort avec Laravel
Laravel Eloquent : insertGetId - insérer et récupérer l'ID d'un enregistrement en une ligne

Laravel offre la possibilité d'insérer un enregistrement et de récupérer son ID en une ligne. Cet article vous explique comment faire.

Laravel Eloquent : insertGetId - insérer et récupérer l'ID d'un enregistrement en une ligne