Saltearse al contenido

Seguridad

Es importante asegurarse de que sus aplicaciones Livewire sean seguras y no expongan ninguna vulnerabilidad. Livewire cuenta con funciones de seguridad internas para gestionar muchos casos, sin embargo, hay ocasiones en las que es el código de su aplicación el que debe garantizar la seguridad de sus componentes.

Autorización de parámetros de acción

Las acciones de Livewire son extremadamente potentes, sin embargo, cualquier parámetro pasado a las acciones de Livewire es mutable en el cliente y debe tratarse como una entrada de usuario no fiable.

Podría decirse que el error de seguridad más común en Livewire es no validar y autorizar las llamadas a acciones de Livewire antes de persistir los cambios en la base de datos.

A continuación se muestra un ejemplo de inseguridad derivada de la falta de autorización:

<?php
use App\Models\Post;
use Livewire\Component;
class ShowPost extends Component
{
// ...
public function delete($id)
{
// INSEGURO!
$post = Post::find($id);
$post->delete();
}
}
<button wire:click="delete({{ $post->id }})">Eliminar Post</button>

La razón por la que el ejemplo anterior no es seguro es que wire:click="delete(...)" puede modificarse en el navegador para pasar CUALQUIER ID de publicación que desee un usuario malintencionado.

Los parámetros de acción (como $id en este caso) deben tratarse igual que cualquier entrada no fiable del navegador.

Por lo tanto, para mantener la seguridad de esta aplicación y evitar que un usuario elimine la publicación de otro usuario, debemos añadir autorización a la acción delete().

En primer lugar, creemos una política de Laravel para el modelo Post ejecutando el siguiente comando:

Ventana de terminal
php artisan make:policy PostPolicy --model=Post

Después de ejecutar el comando anterior, se creará una nueva política dentro de app/Policies/PostPolicy.php. A continuación, podemos actualizar su contenido con un método de eliminación como este:

<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
/**
* Determina si el usuario puede eliminar la publicación dada.
*/
public function delete(?User $user, Post $post): bool
{
return $user?->id === $post->user_id;
}
}

Ahora, podemos usar el método $this->authorize() del componente Livewire para asegurarnos de que el usuario es el propietario de la publicación antes de eliminarla:

public function delete($id)
{
$post = Post::find($id);
// Si el usuario no es el propietario de la publicación,
// se lanzará una excepción AuthorizationException...
$this->authorize('delete', $post);
$post->delete();
}

Más información:

Autorización de propiedades públicas

Al igual que los parámetros de acción, las propiedades públicas en Livewire deben tratarse como entradas no fiables del usuario.

A continuación se muestra el mismo ejemplo anterior sobre la eliminación de una publicación, escrito de forma insegura de una manera diferente:

<?php
use App\Models\Post;
use Livewire\Component;
class ShowPost extends Component
{
public $postId;
public function mount($postId)
{
$this->postId = $postId;
}
public function delete()
{
// INSEGURO!
$post = Post::find($this->postId);
$post->delete();
}
}
<button wire:click="delete">Eliminar Post</button>

Como puede ver, en lugar de pasar el $postId como parámetro al método delete desde wire:click, lo almacenamos como una propiedad pública en el componente Livewire.

El problema con este enfoque es que cualquier usuario malintencionado puede inyectar un elemento personalizado en la página, como por ejemplo:

<input type="text" wire:model="postId">

Esto les permitiría modificar libremente el $postId antes de pulsar “Eliminar Post”. Dado que la acción de delete no autoriza el valor de $postId, el usuario ahora puede eliminar cualquier publicación de la base de datos, sea suya o no.

Para protegerse contra este riesgo, hay dos soluciones posibles:

Usar propiedades del modelo

Al establecer propiedades públicas, Livewire trata los modelos de forma diferente a los valores simples, como cadenas y números enteros. Por ello, si almacenamos todo el modelo de publicación como una propiedad en el componente, Livewire se asegurará de que el ID nunca sea manipulado.

A continuación se muestra un ejemplo de cómo almacenar una propiedad $post en lugar de una simple propiedad $postId:

<?php
use App\Models\Post;
use Livewire\Component;
class ShowPost extends Component
{
public Post $post;
public function mount($postId)
{
$this->post = Post::find($postId);
}
public function delete()
{
$this->post->delete();
}
}
<button wire:click="delete">Eliminar Post</button>

Ahora este componente está protegido, ya que un usuario malintencionado no tiene forma de cambiar la propiedad $post a un modelo Eloquent diferente.

Bloqueo de la propiedad

Otra forma de evitar que las propiedades se establezcan en valores no deseados es utilizar propiedades bloqueadas (locked properties). El bloqueo de propiedades se realiza aplicando el atributo #[Locked]. Ahora, si los usuarios intentan manipular este valor, se generará un error.

Tenga en cuenta que las propiedades con el atributo Locked aún se pueden cambiar en el back-end, por lo que se debe tener cuidado de que las entradas de usuarios no confiables no se pasen a la propiedad en sus propias funciones de Livewire.

<?php
use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\Locked;
class ShowPost extends Component
{
#[Locked]
public $postId;
public function mount($postId)
{
$this->postId = $postId;
}
public function delete()
{
$post = Post::find($this->postId);
$post->delete();
}
}

Autorizar la propiedad

Si no desea utilizar una propiedad modelo en su escenario, por supuesto puede recurrir a autorizar manualmente la eliminación de la publicación dentro de la acción de eliminación:

<?php
use App\Models\Post;
use Livewire\Component;
class ShowPost extends Component
{
public $postId;
public function mount($postId)
{
$this->postId = $postId;
}
public function delete()
{
$post = Post::find($this->postId);
$this->authorize('delete', $post);
$post->delete();
}
}
<button wire:click="delete">Eliminar Post</button>

Ahora, aunque un usuario malintencionado aún puede modificar libremente el valor de $postId, cuando se llama a la acción de delete, $this->authorize() lanzará una excepción AuthorizationException si el usuario no es el propietario de la publicación.

Más información:

Middleware

Cuando se carga un componente Livewire en una página que contiene middleware de autorización a nivel de ruta, como por ejemplo:

Route::get('/post/{post}', App\Livewire\UpdatePost::class)
->middleware('can:update,post');

Livewire se asegurará de que esos middlewares se vuelvan a aplicar a las solicitudes posteriores de la red Livewire. Esto se conoce como “middleware persistente” en el núcleo de Livewire.

El middleware persistente te protege de situaciones en las que las reglas de autorización o los permisos de usuario han cambiado después de la carga inicial de la página.

A continuación, se muestra un ejemplo más detallado de una situación de este tipo:

Route::get('/post/{post}', App\Livewire\UpdatePost::class)
->middleware('can:update,post');
<?php
use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\Validate;
class UpdatePost extends Component
{
public Post $post;
#[Validate('required|min:5')]
public $title = '';
public $content = '';
public function mount()
{
$this->title = $this->post->title;
$this->content = $this->post->content;
}
public function update()
{
$this->post->update([
'title' => $this->title,
'content' => $this->content,
]);
}
}

Como puede ver, el middleware can:update,post se aplica a nivel de ruta. Esto significa que un usuario que no tiene permiso para actualizar una publicación no puede ver la página.

Sin embargo, considere un escenario en el que un usuario:

  • Carga la página.
  • Pierde el permiso para actualizar después de que se carga la página.
  • Intenta actualizar la publicación después de perder el permiso.

Dado que Livewire ya ha cargado correctamente la página, es posible que se pregunte: “Cuando Livewire realice una solicitud posterior para actualizar la publicación, ¿se volverá a aplicar el middleware can:update,post? ¿O, por el contrario, el usuario no autorizado podrá actualizar la publicación correctamente?”.

Dado que Livewire cuenta con mecanismos internos para volver a aplicar el middleware desde el punto final original, usted está protegido en este escenario.

Configuración del middleware persistente

De forma predeterminada, Livewire mantiene el siguiente middleware en todas las solicitudes de red:

<?php
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Laravel\Jetstream\Http\Middleware\AuthenticateSession::class,
\Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\RedirectIfAuthenticated::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Auth\Middleware\Authorize::class,

Si alguno de los middlewares anteriores se aplica a la carga inicial de la página, se mantendrá (se volverá a aplicar) en cualquier solicitud de red futura.

Sin embargo, si estás aplicando un middleware personalizado desde tu aplicación en la carga inicial de la página y deseas que se mantenga entre las solicitudes de Livewire, deberás añadirlo a esta lista desde un proveedor de servicios en tu aplicación de la siguiente manera:

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Livewire;
class AppServiceProvider extends ServiceProvider
{
/**
* Inicie cualquier servicio de aplicación.
*/
public function boot(): void
{
Livewire::addPersistentMiddleware([
App\Http\Middleware\EnsureUserHasRole::class,
]);
}
}

Si se carga un componente Livewire en una página que utiliza el middleware EnsureUserHasRole de su aplicación, ahora se mantendrá y se volverá a aplicar a cualquier solicitud de red futura a ese componente Livewire.

Aplicación del middleware global Livewire

Alternativamente, si desea aplicar un middleware específico a cada solicitud de red de actualización de Livewire, puede hacerlo registrando su propia ruta de actualización de Livewire con cualquier middleware que desee:

Livewire::setUpdateRoute(function ($handle) {
return Route::post('/livewire/update', $handle)
->middleware(App\Http\Middleware\LocalizeViewPaths::class);
});

Cualquier solicitud Livewire AJAX/fetch realizada al servidor utilizará el punto final anterior y aplicará el middleware LocalizeViewPaths antes de gestionar la actualización del componente.

Obtenga más información sobre cómo personalizar la ruta de actualización en la página Instalación.

Sumas de comprobación de instantáneas

Entre cada solicitud de Livewire, se toma una instantánea del componente Livewire y se envía al navegador. Esta instantánea se utiliza para reconstruir el componente durante la siguiente ronda del servidor.

Obtenga más información sobre las instantáneas de Livewire en la documentación de Hydration.

Dado que las solicitudes fetch pueden ser interceptadas y manipuladas en un navegador, Livewire genera una “suma de comprobación” de cada instantánea para acompañarla.

Esta suma de comprobación se utiliza en la siguiente solicitud de red para verificar que la instantánea no ha cambiado de ninguna manera.

Si Livewire encuentra una discrepancia en la suma de comprobación, lanzará una excepción CorruptComponentPayloadException y la solicitud fallará.

Esto protege contra cualquier forma de manipulación maliciosa que, de otro modo, daría a los usuarios la posibilidad de ejecutar o modificar código no relacionado.