Skip to content

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.

  1. Al generar una página, Laravel crea un token único y aleatorio para la sesión del usuario.
  2. Este token se incrusta en los formularios HTML.
  3. Al enviar el formulario, el token se envía junto con la solicitud.
  4. En el servidor, el middleware VerifyCsrfToken compara el token de la solicitud con el token almacenado en la sesión.
  5. 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:

<input type="hidden" name="_token" value="j2aK3dLf4gH5...уникальный_токен...">

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

1. ¿Qué tipo de ataque previene el token CSRF?

2. ¿Qué directiva Blade añade un campo oculto con el token CSRF a un formulario?

3. ¿Qué sucederá si se envía una solicitud POST sin un token CSRF a una ruta web?

4. ¿Qué encabezado HTTP estándar se utiliza para enviar el token CSRF en solicitudes AJAX?

5. ¿Por qué las rutas API (`routes/api.php`) no utilizan la protección CSRF por defecto?


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