Capítulo 5.4: Trabajar con tokens CSRF
Tiempo de estudio: 30 minutos
1. ¿Qué es un ataque CSRF? El "secuestro" de tu nave
Imagina que has iniciado sesión en el panel de control de tu flota espacial (space-api.test
). En una pestaña adyacente, abres un sitio web inofensivo con gatitos (evil-cats.com
). En este sitio web hay un formulario oculto que envía automáticamente una solicitud a tu sitio web a la dirección POST /api/planets/1/delete
.
Dado que ya estás autenticado en space-api.test
, tu navegador adjuntará amablemente todas tus cookies a esta solicitud. El servidor Laravel verá una sesión válida y pensará que fuiste tú quien decidió dar de baja el planeta. El planeta será eliminado sin tu conocimiento.
Esto es CSRF (Cross-Site Request Forgery) — un ataque en el que un atacante obliga al navegador de un usuario autenticado a realizar una acción no deseada en un sitio de confianza.
💡 Analogía espacial:
Eres el capitán de una nave, y tienes una tarjeta de acceso (sesión/cookie). El atacante no puede robar tu tarjeta. Pero puede engañarte para que la acerques a un terminal de desmantelamiento de recursos mientras estás distraído. El token CSRF es como un código PIN que debes ingresar junto con la tarjeta. El atacante no sabe el código PIN, y su ataque falla.
2. ¿Cómo protege Laravel contra CSRF?
Laravel protege por defecto todas las solicitudes web "inseguras" (POST, PUT, PATCH, DELETE) utilizando un token CSRF.
- Al generar una página, Laravel crea un token único y aleatorio para la sesión del usuario.
- Este token se incrusta en los formularios HTML.
- Al enviar el formulario, el token se envía junto con la solicitud.
- En el servidor, el middleware
VerifyCsrfToken
compara el token de la solicitud con el token almacenado en la sesión. - Si los tokens no coinciden, Laravel interrumpe la solicitud con un error 419 (Session Expired/Page Expired).
Importante: Las rutas API en routes/api.php
no están protegidas por CSRF, ya que asumen un mecanismo de autenticación diferente (por ejemplo, tokens de Sanctum), y no sesiones basadas en cookies. Nuestro problema actual se refiere específicamente a las rutas web y las páginas que creamos en routes/web.php
.
3. Uso del token CSRF en formularios HTML
Este es el escenario más simple. Laravel proporciona una directiva Blade especial para esto.
Ejemplo: Formulario para crear un planeta
Creemos un formulario simple en el archivo resources/views/planets/create.blade.php
:
<h2>Formulario de lanzamiento de nuevo planeta</h2>
<form action="/planets" method="POST">
@csrf {{-- ¡Aquí está la magia! --}}
<label for="name">Nombre:</label>
<input type="text" id="name" name="name" required>
<label for="solar_system">Sistema solar:</label>
<input type="text" id="solar_system" name="solar_system" required>
{{-- ... otros campos ... --}}
<button type="submit">Lanzar</button>
</form>
La directiva @csrf
generará automáticamente un campo oculto en el formulario:
Esto es suficiente para proteger formularios HTML estándar.
4. Uso del token CSRF en solicitudes AJAX/Fetch
En el capítulo anterior, enviamos una solicitud DELETE
usando JavaScript. Ahora Laravel la bloqueará con un error 419. Necesitamos añadir el token CSRF en los encabezados de nuestra solicitud Fetch.
Paso 1: Hacer el token disponible para JavaScript
Añade la metaetiqueta con el token en el <head>
de tu plantilla maestra resources/views/app.blade.php
. Esta es una práctica estándar en Laravel.
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{-- Añadimos el token CSRF en la metaetiqueta --}}
<meta name="csrf-token" content="{{ csrf_token() }}">
{{-- ... --}}
</head>
La función csrf_token()
devuelve el token actual.
Paso 2: Modificamos JavaScript para enviar el token
Ahora, en nuestro public/js/planets.js
podemos leer este token y añadirlo a los encabezados de todas las solicitudes "inseguras".
// ... en el archivo public/js/planets.js ...
document.addEventListener('DOMContentLoaded', () => {
// Obtenemos el token de la metaetiqueta
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(button => {
button.addEventListener('click', async (event) => {
// ... lógica de confirmación ...
try {
const response = await fetch(apiUrl, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': csrfToken // <-- ¡Añadimos el token en los encabezados!
}
});
// ... el resto de la lógica de procesamiento de la respuesta ...
} catch (error) {
// ...
}
});
});
});
- El nombre del encabezado
X-CSRF-TOKEN
es el estándar que Laravel verifica por defecto.
Ahora nuestras solicitudes AJAX también están protegidas. Intenta eliminar el planeta de nuevo — esta vez la solicitud se ejecutará correctamente.
Cuestionario para consolidar
🚀 Resumen del capítulo:
Has instalado un "sistema de identificación amigo-enemigo" en tu nave espacial, protegiéndola de ataques CSRF. Has aprendido a:
- Comprender la esencia y el peligro de los ataques CSRF.
- Proteger formularios HTML estándar con la directiva
@csrf
. - Transferir el token CSRF a JavaScript a través de una metaetiqueta.
- Incluir el token en las cabeceras de las solicitudes AJAX/Fetch para su ejecución exitosa.
Tus interfaces web ahora no solo son interactivas, sino también seguras. En el siguiente capítulo, completaremos la creación de nuestra interfaz web, viendo cómo organizar correctamente el enrutamiento para las páginas web.