domingo, 24 de agosto de 2025

Fetch de Javascript y redirección de autenticación del backend

 Estoy haciendo un fetch desde el navegador hacia una URL definida en api.php (una ruta de API). Me devuelve 302 found. Después, mi fetch recibe el siguiente error: SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON. ¿Qué pasa?

Lo que te está ocurriendo es una redirección de autenticación.

El error Unexpected token '<' no se debe a un problema en tu código JavaScript, sino a que tu fetch está recibiendo una página HTML completa, no la respuesta JSON que esperas.

Esto es lo que pasa, paso a paso:


El Problema

  1. Haces la petición fetch: Tu código JavaScript en el navegador envía una solicitud a tu ruta de API, por ejemplo, /api/informacion-protegida.

  2. El servidor recibe la petición: El middleware de autenticación (auth:sanctum) en Laravel intercepta la solicitud.

  3. La verificación falla: El middleware detecta que la solicitud no está autenticada (por ejemplo, no se envió la cookie de sesión o el token XSRF, o la sesión ha expirado).

  4. Laravel redirige: Laravel, en lugar de devolver un error JSON (como un 401 Unauthorized), realiza una redirección HTTP 302 a la página de inicio de sesión (/login o similar).

  5. El fetch sigue la redirección: Por defecto, la mayoría de los clientes HTTP (incluido fetch en el navegador) siguen las redirecciones automáticamente. Tu fetch hace una segunda solicitud a la nueva URL (/login).

  6. Recibes una página HTML: El servidor de Laravel responde a la solicitud de /login con una página HTML completa (la página de inicio de sesión). El contenido empieza con <!DOCTYPE html>.

  7. Fallo al procesar JSON: Tu código JavaScript intenta leer el cuerpo de la respuesta con response.json(). Como la respuesta no es un JSON válido, sino un HTML, el analizador de JSON falla y arroja el error SyntaxError: Unexpected token '<'.


La Solución

Para evitar este problema, necesitas asegurarte de que tu fetch envía las cabeceras de Inertia correctas y de que el servidor está configurado para no redirigir en peticiones de API.

  1. Asegúrate de que la petición fetch envíe la cabecera X-Inertia:

    Esta es la cabecera clave que le dice al servidor que no debe redirigir a una página HTML, sino que debe enviar una respuesta JSON de error (401 Unauthorized).

    JavaScript
    // En tu fetch
    fetch('/api/informacion-protegida', {
      method: 'GET',
      headers: {
        'Accept': 'application/json', // Opcional, pero buena práctica
        'X-Inertia': 'true',         // Esta es la cabecera clave
        // Añade el token XSRF si es necesario (para POST, PUT, etc.)
        'X-XSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
      }
    })
    .then(response => {
        // Maneja la respuesta. El status 401 es lo que esperas.
        if (response.status === 401) {
            console.error('Acceso no autorizado. Redirigiendo...');
            // Redirige al usuario al login
            window.location.href = '/login';
        } else {
            return response.json();
        }
    })
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        // Este catch ya no capturará el SyntaxError
        console.error('Error en la petición:', error);
    });
    
  2. Verifica la configuración del middleware de API:

    Asegúrate de que tus rutas de API estén dentro del grupo de middleware api en routes/api.php y que el middleware auth:sanctum esté correctamente aplicado. Laravel, por defecto, sabe cómo manejar estas peticiones y devolverá un 401 Unauthorized en lugar de una redirección.


Resumen

El error no es que tu código esté mal, sino que la respuesta que recibes no es lo que esperas. La redirección del servidor es el problema raíz, y el error de JSON es una consecuencia de que el fetch intenta analizar una página web.

Al usar la cabecera X-Inertia: true en tu petición, le indicas al servidor que debe comportarse como una API y enviar una respuesta JSON, lo que te permite manejar el error de autenticación de forma programática.


¿Cómo hago que mi fetch envíe la cookie de sesión?

Tu fetch ya lo hace de forma automática. La cookie de sesión es un mecanismo integrado en el navegador que se encarga de enviarla con cada solicitud al mismo dominio.

Esto significa que no necesitas hacer ningún cambio en tu código JavaScript para enviar la cookie. El navegador se encarga de todo el trabajo por ti.


¿Cómo funciona?

  1. Inicio de sesión: Cuando un usuario inicia sesión, el servidor de Laravel crea una cookie de sesión y la envía al navegador.

  2. Almacenamiento: El navegador almacena esta cookie.

  3. Envío automático: Con cada nueva petición que hagas desde tu página (por ejemplo, con fetch o con una navegación normal) hacia la misma URL de origen (el mismo dominio), el navegador adjunta automáticamente la cookie de sesión en las cabeceras de la solicitud.

  4. Verificación del servidor: El servidor de Laravel recibe la solicitud, lee la cookie de sesión y, si es válida, sabe que el usuario está autenticado.


¿Entonces por qué recibía un error?

El problema que mencionaste (SyntaxError) no era que no se enviara la cookie, sino que el servidor estaba redirigiendo la solicitud antes de que la verificara. La redirección 302 a una página de inicio de sesión es el comportamiento predeterminado de Laravel cuando una solicitud no está autenticada.

La solución fue agregar la cabecera X-Inertia: true, que le dice al servidor que la solicitud no viene de una navegación normal, sino de una SPA, por lo que debe devolver un error JSON (401 Unauthorized) en lugar de redirigir a una página HTML.

En resumen, no te preocupes por la cookie de sesión. El navegador la gestiona por ti. Lo importante es que tu fetch esté configurado para manejar la respuesta del servidor de Inertia.


Ahora que puse la cabecera X-Inertia=true, recibo como respuesta un 401 Unauthenticated. Sin embargo, mi request incluye tanto la cookie de sesión como el token XSRF, tanto en formato cookie como en la cabecera. Mi ruta de API está protegida por el middleware auth:sanctum. ¿Por qué razón podría recibir un 401 Unauthenticated?

¡Entendido! Esto es un problema clásico que, a menudo, no tiene que ver con la cookie en sí, sino con cómo Laravel valida la sesión de la API. Hay una razón principal y un par de secundarias por las que esto podría estar ocurriendo.


La razón principal: Sanctum y stateful

Laravel Sanctum, por defecto, solo verifica la autenticación basada en cookies para dominios que considera stateful (con estado). Esto se configura en el archivo config/sanctum.php.

Por defecto, la configuración sanctum.stateful se ve así:

PHP
// config/sanctum.php
'stateful' => [
    'localhost',
    'localhost:3000',
    '127.0.0.1',
    '127.0.0.1:8000',
    '::1',
    // Tus dominios adicionales aquí...
],

El problema es que si tu aplicación de frontend se ejecuta en un subdominio diferente o un dominio completamente distinto (por ejemplo, sub.tu-app.test o tu-frontend.com) al de tu API (api.tu-app.test), Laravel no considerará que la solicitud es "con estado" y no intentará validar la sesión, devolviendo un 401 Unauthenticated.

Solución:

Asegúrate de que el dominio de tu frontend está incluido en el array stateful en el archivo de configuración de Sanctum.


Otras posibles razones

  1. La cookie no se envía: Aunque el navegador la envía automáticamente, hay escenarios en los que no lo hace:

    • Problemas de CORS: Si estás haciendo la petición desde un dominio diferente, debes asegurarte de que tu servidor esté configurado correctamente para permitir el Access-Control-Allow-Credentials. Sin esto, el navegador no enviará la cookie de sesión.

    • Configuración del fetch: Aunque no lo hiciste antes, si en algún momento configuraste fetch con credentials: 'omit' en tus opciones, esto impedirá que se envíen las cookies. Asegúrate de que está configurado como same-origin (el valor por defecto) o include.

  2. El token XSRF no coincide: El token XSRF es clave para la seguridad.

    • Obtención incorrecta: Es posible que no estés obteniendo el token de la forma correcta (del meta tag o de la cookie XSRF-TOKEN). Si la URL de la API es diferente a la de la página web, el token de la cookie puede no estar disponible.

    • La sesión expiró: Si la sesión expiró en el servidor, el token ya no será válido, incluso si todavía lo tienes en tu frontend.

En resumen, el 401 que recibes es una señal de que Laravel no ve la sesión como válida. Lo más probable es que sea un problema de configuración de dominios en Sanctum, pero también vale la pena revisar la configuración de CORS y la obtención del token XSRF para asegurarte de que todo está en su lugar.


¿En un request generado con fecth son componentes distintas los headers y las cookies?

Sí, son dos componentes completamente distintos.


Cookies y Headers en un fetch

La diferencia clave es cómo y por quién son gestionados:

  • Headers (Cabeceras): Son parte de la solicitud HTTP en sí. Son una serie de pares clave-valor que se envían junto con la URL y el método de la solicitud. En JavaScript, puedes controlarlas y modificarlas directamente en las opciones de tu llamada fetch. Las cabeceras como Content-Type, Authorization y X-Inertia se definen y se envían de forma explícita en tu código.

  • Cookies: Son pequeños archivos de texto que el navegador almacena y gestiona. No son parte de la solicitud en sí, sino un mecanismo que el navegador usa para enviar datos de sesión al servidor en cada petición. El navegador se encarga automáticamente de adjuntar las cookies relevantes a la solicitud si la URL de destino es del mismo dominio y no hay restricciones de seguridad. Tú, como desarrollador, no tienes que añadirlas explícitamente en el código de tu fetch.

En resumen, los headers son algo que tú controlas, mientras que las cookies son algo que el navegador maneja por ti.


El Token XSRF

El token XSRF es un caso especial porque existe en ambos lugares:

  1. Como cookie: El servidor lo envía en la cookie XSRF-TOKEN.

  2. Como header: Tu código JavaScript debe leer el valor de esa cookie (o de la metaetiqueta CSRF en el HTML) y enviarlo manualmente en la cabecera X-XSRF-TOKEN para que el servidor pueda validarlo.

Por lo tanto, mientras que la cookie de sesión se gestiona automáticamente, el token XSRF requiere una intervención manual para ser enviado correctamente en una cabecera, cumpliendo con la seguridad que pide Laravel.


Cuando quito el middleware auth:sanctum el request llega a mi controller. Pero cuando lo pongo, el fetch de mi navegador recibe un 401 Unauthenticated. No entiendo por qué Sanctum está rechazando mi request. Mi aplicación corre en localhost:8000. El valor de mi config.sanctum es

0 = "localhost"

1 = "localhost:3000"

2 = "127.0.0.1"

3 = "127.0.0.1:8000"

4 = "::1"

5 = "localhost:8000"

¿Puedo ver qué hace Sanctum por dentro para entender el motivo del rechazo del request?

Excelente pregunta. Entender lo que hace Sanctum "por dentro" es clave para depurar este tipo de problemas. El error 401 Unauthenticated significa que, en alguna de las verificaciones internas, la solicitud falló la prueba de autenticación.

Aquí te explico, paso a paso, el proceso interno de validación de Sanctum para una solicitud de SPA (Single-Page Application).


El Flujo de Autenticación de Sanctum

La autenticación de Sanctum para las SPA no se basa en un solo "switch", sino en una serie de verificaciones que ocurren dentro del middleware EnsureFrontendRequestsAreStateful.

1. Verificación del X-Requested-With:

La primera y más básica prueba es verificar la presencia de la cabecera X-Requested-With con el valor XMLHttpRequest. Esta es la forma en que Laravel determina que la petición es una solicitud AJAX y no una navegación normal del navegador. Si esta cabecera no está presente, Sanctum asume que no es una solicitud de SPA y la autenticación fallará.

2. Verificación del Dominio stateful:

A continuación, Sanctum comprueba si el dominio desde el que se origina la solicitud está en el array sanctum.stateful. Tu configuración parece correcta, pero es un punto crucial de fallo. Por ejemplo, si el Referer de la solicitud es http://localhost:8000/login y el Origin es http://localhost:8000, Sanctum debe verificar que ambos están en la lista.

3. Verificación de la Cookie de Sesión:

Si las dos verificaciones anteriores pasan, Sanctum examina la solicitud en busca de la cookie de sesión (laravel_session). Si la cookie está presente y contiene un ID de sesión válido que corresponde a un usuario autenticado en el servidor, ¡la autenticación es un éxito!

4. Verificación del Token XSRF:

Si la petición es una solicitud mutadora (como POST, PUT, DELETE), Laravel también validará que el token XSRF enviado en la cabecera X-XSRF-TOKEN coincide con el token en la cookie XSRF-TOKEN. Si no coinciden, la solicitud será rechazada.


¿Por qué sigues recibiendo un 401?

Dado que tu configuración de stateful es correcta, el problema más probable no está en la configuración de Sanctum, sino en un detalle de la petición real que estás enviando.

Lo que está sucediendo es que tu fetch no está enviando una de las cabeceras requeridas para que Sanctum inicie su proceso de autenticación con estado.

Es probable que falte la cabecera X-Requested-With: XMLHttpRequest.

Asegúrate de que tu fetch incluya esta cabecera explícitamente, ya que muchos clientes HTTP no la añaden por defecto.

Aquí tienes un ejemplo de cómo debe verse tu código fetch:

JavaScript
fetch('/api/mi-ruta-protegida', {
  method: 'GET', // O 'POST', 'PUT', etc.
  headers: {
    // Esto le dice a Sanctum que es una petición AJAX con estado.
    'X-Requested-With': 'XMLHttpRequest',
    'X-Inertia': 'true', // Opcional, pero buena práctica con Inertia.
    // Asegúrate de enviar también el token CSRF para peticiones mutadoras.
    'X-XSRF-TOKEN': document.cookie.split('XSRF-TOKEN=')[1].split(';')[0],
  }
})
.then(response => {
  if (response.status === 401) {
    console.error('Petición no autenticada.');
  } else {
    return response.json();
  }
})
.then(data => {
  // Maneja tus datos aquí
})
.catch(error => {
  console.error('Error de red:', error);
});

En resumen, el 401 Unauthenticated no es un error de "credenciales incorrectas", sino un error de "esta petición no cumple los requisitos para ser tratada como una sesión de SPA". Al añadir las cabeceras correctas, le das a Sanctum toda la información que necesita para validar la sesión de forma correcta.


La URL desde donde hago el fetch (El Referer) es una URL formada por parámetros dinámicos (/odontograma/{paciente}). No puedo agregar todos los valores posibles para {paciente} al array config(sanctum.stateful). ¿Cuál es la manera de configurar sanctum.stateful cuando el Referer es una URL con parte dinámica?

Para Sanctum, el Referer no es lo que usa para la validación. Lo que realmente importa es el origen de la petición (Origin).

La validación que hace Sanctum es a nivel de dominio, no de ruta completa. La configuración de SANCTUM_STATEFUL_DOMAINS en tu archivo .env o config/sanctum.php debe ser el dominio de tu frontend, no la ruta completa.


Solución

En tu caso, la solución es muy simple: solo debes incluir el dominio y el puerto de tu aplicación. El camino (/odontograma/{paciente}) es irrelevante para Sanctum.

Si tu aplicación se ejecuta en http://localhost:8000, tu archivo .env debería contener algo como esto:

Code snippet
SANCTUM_STATEFUL_DOMAINS=localhost:8000

Si estás usando un servidor de desarrollo de Vite en un puerto diferente (por ejemplo, http://localhost:5173), tu archivo debería ser:

Code snippet
SANCTUM_STATEFUL_DOMAINS=localhost:5173

El middleware de Sanctum simplemente compara el encabezado Origin de la solicitud con los dominios configurados en SANCTUM_STATEFUL_DOMAINS. Mientras el dominio de tu frontend coincida, no importa si la URL es /odontograma/123, /odontograma/abc o cualquier otra.

¿Qué pasa en producción?

Cuando despliegues tu aplicación, la lógica es la misma. Si tu frontend se sirve desde https://app.mi-dominio.com, tu configuración .env en el servidor de producción debería ser:

Code snippet
SANCTUM_STATEFUL_DOMAINS=app.mi-dominio.com

Con esta configuración, no importa a qué URL dinámica de tu API acceda tu frontend, ya que la validación se basa en el dominio, no en el path. Es una forma robusta de manejar la seguridad en aplicaciones de una sola página.



¿Tengo que ocuparme de la cuestión CORS si tanto el frontend como el backend están en el mismo dominio y subdominio?

No, si tu frontend y tu backend están en el mismo dominio (por ejemplo, mi-app.test) y subdominio (por ejemplo, www.mi-app.test), generalmente no tienes que preocuparte por los problemas de CORS.


¿Por qué?

CORS, que significa "Cross-Origin Resource Sharing" (Intercambio de recursos de origen cruzado), es un mecanismo de seguridad implementado en los navegadores web. Su único propósito es evitar que un dominio malicioso (dominio-malo.com) pueda hacer solicitudes a tu servidor (mi-app.test) y robar datos sin tu permiso.

Cuando tu frontend y tu backend están en el mismo origen, es decir, comparten el mismo:

  • Protocolo (http o https)

  • Dominio (mi-app.test)

  • Puerto (8000 si lo usas)

...el navegador asume que confías en ti mismo y no aplica las restricciones de CORS. El navegador envía la solicitud, incluyendo la cookie de sesión, sin necesidad de las cabeceras CORS (Access-Control-Allow-Origin, etc.).


Excepciones donde CORS podría ser relevante

Aunque no lo creas, hay algunas situaciones en las que podrías encontrarte con un problema de CORS incluso en tu entorno local:

  1. Puertos diferentes: Si tu frontend corre en un puerto (localhost:3000) y tu backend en otro (localhost:8000), el navegador los considera orígenes diferentes. En este caso, tendrías que configurar el middleware de CORS de Laravel para permitir las peticiones de tu frontend.

  2. Subdominios distintos: Si tu frontend está en app.mi-app.test y tu API en api.mi-app.test, el navegador los tratará como orígenes distintos. De nuevo, necesitarías una configuración CORS.

  3. Ambientes de producción: Cuando tu aplicación se despliegue, el dominio cambiará. Si la API se mantiene en un subdominio o un dominio diferente, necesitarás la configuración CORS.

Tu caso (localhost:8000) es uno de los escenarios más sencillos donde CORS no se aplica. El problema que tuviste antes se debía exclusivamente a las reglas de autenticación de Sanctum y no a las políticas del navegador.


Todavía sigue sin funcionar. ¿Sería de ayuda utilizar Axios con la siguiente configuración

axios.defaults.withCredentials = true;

axios.defaults.withXSRFToken = true;

?

Sí, definitivamente. Si estás usando Axios, esa configuración es exactamente lo que necesitas para que la autenticación con Sanctum funcione correctamente.


¿Qué hace esa configuración?

  1. axios.defaults.withCredentials = true;

    Esta es la clave para las cookies. Le dice a Axios que debe incluir las cookies y las cabeceras de autorización en las peticiones que se hagan a otros dominios. Aunque tu frontend y backend estén en el mismo origen (localhost:8000), esta opción asegura que las cookies de sesión se envíen con cada solicitud, que es el comportamiento que necesitamos para que Sanctum funcione. Es una buena práctica para cualquier aplicación con arquitectura SPA.

  2. axios.defaults.withXSRFToken = true;

    Esta opción le indica a Axios que debe buscar el token XSRF y adjuntarlo automáticamente a las cabeciones de las peticiones. Esto es crucial para las solicitudes POST, PUT y DELETE, ya que Laravel no las procesará si no hay un token XSRF válido. Axios buscará el token en la cookie XSRF-TOKEN y lo agregará a la cabecera X-XSRF-TOKEN.

Conclusión

El error 401 Unauthenticated que recibes es casi con certeza porque, por defecto, Axios no envía las credenciales (cookies) en las peticiones de origen cruzado. Al establecer withCredentials = true, le estás indicando a Axios que sí lo haga, permitiendo que la cookie de sesión llegue al servidor y que Sanctum pueda autenticar la petición.

Con esta configuración, ya no deberías tener problemas para acceder a tus rutas de API protegidas.


No sé si la barra de herramientas del desarrollador web (web developer toolbar) me puede mostrar si se están enviando la cookie de sesión y la cabecera con el token xsrf.

Sí, la barra de herramientas del desarrollador web es tu mejor amiga para esto. Te voy a guiar paso a paso sobre cómo verificarlo en Chrome (las herramientas de otros navegadores son muy similares).


Pasos para verificar las cabeceras y cookies

  1. Abre las Herramientas del Desarrollador:

    • Haz clic derecho en cualquier lugar de la página y selecciona "Inspeccionar".

    • O usa el atajo de teclado: Ctrl + Shift + I (Windows/Linux) o Cmd + Option + I (Mac).

  2. Ve a la pestaña "Red" (Network):

    • Esta pestaña registra todas las solicitudes HTTP que hace tu página. Manténla abierta mientras interactúas con la aplicación.

  3. Realiza la solicitud que quieres verificar:

    • Haz clic en el botón o activa el evento que dispara la petición a tu API. Por ejemplo, el botón de "Iniciar sesión".

    • Verás una lista de solicitudes que aparecen en la pestaña de "Red".

  4. Selecciona la solicitud de la API:

    • Busca la solicitud que va a tu backend. Su nombre será la URL de tu API (ej: login, user, etc.). Haz clic en ella.

    • Se abrirá un panel lateral con los detalles de la solicitud.

  5. Revisa las "Cabeceras de la Solicitud" (Request Headers):

    • En este panel, ve a la pestaña "Encabezados" (Headers).

    • Desplázate hacia abajo hasta la sección "Request Headers". Aquí verás todas las cabeceras que el navegador ha enviado al servidor.

    • Busca la cabecera X-XSRF-TOKEN. Si tu configuración de Axios está funcionando, deberías ver esta cabecera con un valor largo y alfanumérico.

  6. Revisa la "Cookie":

    • Justo encima o debajo de las "Request Headers" puedes encontrar la sección de "Cookies".

    • Asegúrate de que la cookie XSRF-TOKEN y la cookie de sesión (laravel_session por defecto) estén presentes y tengan un valor.

Con estos pasos, puedes confirmar visualmente que el frontend está enviando tanto la cabecera del token XSRF como las cookies de sesión. Si no aparecen, la configuración de Axios no está funcionando como debería, y el problema no está en Laravel, sino en la forma en que el navegador y Axios interactúan antes de la solicitud.


Explicame las 3 categorías que tiene la pestaña Headers de la barra de herramientas del desarrollador web (web developer toolbar). Las 3 categorías son: General, Response Headers y Request Headers.

La pestaña "Encabezados" (Headers) en las herramientas de desarrollador es fundamental para depurar peticiones, ya que te muestra toda la información sobre la comunicación HTTP. Se divide en tres categorías principales que te ayudan a entender el flujo de datos.

1. General

La sección "General" te ofrece una visión general de la petición y su respuesta. Es la primera parada para verificar que la petición se está haciendo correctamente. Muestra información de alto nivel como:

  • Request URL (URL de la Solicitud): La dirección completa a la que se envió la petición.

  • Request Method (Método de la Solicitud): El método HTTP utilizado (por ejemplo, GET, POST, PUT, DELETE).

  • Status Code (Código de Estado): El código de respuesta HTTP devuelto por el servidor (por ejemplo, 200 OK, 401 Unauthorized, 404 Not Found). Este es crucial para saber si la petición fue exitosa o falló.

  • Remote Address (Dirección Remota): La dirección IP y el puerto del servidor con el que se está comunicando el navegador. Como mencionaste, aquí puedes ver la diferencia entre localhost y 127.0.0.1.


2. Response Headers

Los "Encabezados de Respuesta" (Response Headers) son los datos que el servidor te envía de vuelta. Estos encabezados son como una carta de presentación del servidor. Proporcionan información sobre cómo manejar la respuesta, cómo se codificó y las políticas de seguridad que se aplican.

Algunos ejemplos de encabezados de respuesta importantes son:

  • Content-Type: Indica el tipo de medio de la respuesta, como application/json o text/html.

  • Set-Cookie: Es la cabecera más importante para tu caso. El servidor la usa para enviar una cookie al navegador. Aquí es donde verás la cookie de sesión (laravel_session) y el token XSRF (XSRF-TOKEN).

  • Access-Control-Allow-Origin: Esta cabecera es la que controla las políticas de CORS. Si el navegador está haciendo una petición de origen cruzado, el servidor debe incluir esta cabecera para permitirla.


3. Request Headers

Los "Encabezados de Solicitud" (Request Headers) son los datos que el navegador envía al servidor como parte de tu petición. Esta sección es vital para depurar problemas de autenticación, ya que aquí puedes ver la información que el frontend está proporcionando.

Aquí es donde verificarás si tu configuración de Axios está funcionando:

  • Cookie: Esta cabecera es la contraparte de Set-Cookie. El navegador la utiliza para enviar las cookies que ya tiene guardadas de ese dominio. Aquí es donde verás si la cookie de sesión y el token XSRF se están enviando correctamente.

  • X-XSRF-TOKEN: Es la cabecera que Axios crea y envía automáticamente cuando withXSRFToken está activado. Laravel busca este valor para verificar que la petición no sea una falsificación.

  • User-Agent: Identifica el navegador y el sistema operativo que se están utilizando.

  • Accept: Indica al servidor qué tipos de datos puede aceptar el navegador (por ejemplo, application/json).

En resumen, la pestaña "Headers" es un mapa detallado de la comunicación entre el navegador y el servidor, y te permite verificar, en cada paso, si la información necesaria para la autenticación se está enviando y recibiendo como debería.


Mostrame cómo armo un request POST con todo lo necesario para que Sanctum lo dé por autenticado.

Para que Sanctum te autentique una petición POST hecha con fetch, necesitas seguir tres pasos cruciales:

  1. Obtener la cookie del token CSRF: Hacer una petición GET a la ruta sanctum/csrf-cookie.

  2. Leer el token de la cookie: Extraer el valor del token de la cookie XSRF-TOKEN usando JavaScript.

  3. Hacer la petición POST: Enviar la petición POST con las credenciales y el token en la cabecera X-XSRF-TOKEN.

Aquí te muestro cómo se vería el código completo.

// Paso 1: Obtener la cookie del token CSRF.
// El navegador almacenará automáticamente la cookie XSRF-TOKEN.
async function getCsrfCookie() {
await fetch('http://localhost:8000/sanctum/csrf-cookie');
}

// Paso 2: Función auxiliar para leer una cookie específica.
function getCookie(name) {
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for(let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1, c.length);
}
if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length, c.length);
}
}
return null;
}

// Paso 3: Hacer el request POST con la autenticación correcta.
async function postAuthenticatedRequest(data) {
try {
// Primero, asegura que la cookie esté disponible.
// Llama a esta función antes de cualquier petición POST,
// por ejemplo, al cargar la página o al iniciar sesión.
await getCsrfCookie();

// Luego, obtén el valor del token de la cookie.
const xsrfToken = getCookie('XSRF-TOKEN');
// Si no hay token, el usuario no está autenticado o algo falló.
if (!xsrfToken) {
console.error('XSRF token no encontrado. La autenticación puede haber fallado.');
return;
}

const response = await fetch('http://localhost:8000/api/some-endpoint', {
method: 'POST',
// Esta es la clave para enviar las cookies de sesión.
credentials: 'include',

// Incluye el token XSRF en las cabeceras.
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': xsrfToken,
},
body: JSON.stringify(data)
});

if (response.ok) {
const result = await response.json();
console.log('Petición exitosa:', result);
} else {
console.error('Petición fallida. Código de estado:', response.status);
}

} catch (error) {
console.error('Error en la petición:', error);
}
}

// Ejemplo de uso:
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};

postAuthenticatedRequest(userData);

¿Por qué funciona este código?

  • getCsrfCookie(): Esta función hace una simple petición a una ruta de Laravel que está diseñada para establecer la cookie XSRF-TOKEN.

  • credentials: 'include': Esta opción le dice al navegador que incluya cualquier cookie asociada con el dominio (localhost:8000) en la petición. Sin esto, el navegador no enviará la cookie de sesión de Laravel ni el token XSRF.

  • X-XSRF-TOKEN: Al incluir esta cabecera con el valor que obtuviste de la cookie, le demuestras a Laravel que la petición proviene de tu propio frontend y no de un sitio malicioso, superando así el check de autenticación de Sanctum.


Al obtener una cookie con el método que mostrás, ¿no hay que aplicarle un decodeURIComponent?

No, no debes aplicar decodeURIComponent al valor de la cookie XSRF-TOKEN antes de enviarla en la cabecera.


Explicación

El proceso de autenticación con Sanctum funciona de la siguiente manera:

  1. Laravel establece la cookie: Cuando haces la petición a sanctum/csrf-cookie, Laravel toma el token CSRF y lo codifica usando URL encoding (ej. los caracteres / se convierten en %2F) antes de guardarlo en la cookie XSRF-TOKEN.

  2. El navegador almacena la cookie: Tu navegador guarda esta cookie codificada. La función getCookie que te proporcioné lee este valor tal cual, con su codificación.

  3. Tu código envía el valor: En tu petición fetch, tomas ese valor codificado y lo pones en la cabecera X-XSRF-TOKEN.

  4. Laravel decodifica la cabecera: Cuando la petición llega al servidor, el middleware de Laravel (VerifyCsrfToken) lee el valor de la cabecera X-XSRF-TOKEN y lo decodifica automáticamente antes de compararlo con el token que tiene en la sesión.

Si tú le aplicaras decodeURIComponent en el frontend, el valor enviado en la cabecera ya estaría decodificado. El servidor intentaría decodificarlo de nuevo, y el valor final no coincidiría con el token original, lo que resultaría en un error de autenticación (generalmente un 419 Page Expired).

En resumen, la función getCookie está bien como está porque recupera el valor exacto que el servidor espera recibir.


Quiero saber si usar Sanctum para proteger las rutas de API es algo que se debe hacer en mi caso, para hacer un llamado fetch (AJAX) desde una app web de Inertia.

Sí, usar Sanctum para la autenticación de APIs es la opción correcta y recomendada para una aplicación web que usa Inertia.


¿Por qué Inertia y Sanctum son una buena combinación?

Inertia es un adaptador que te permite construir una aplicación de una sola página (SPA) usando Laravel (backend) y frameworks de frontend como Vue o React. Sanctum está diseñado específicamente para autenticar peticiones de una SPA desde el mismo dominio de la aplicación.

Aquí te explico por qué encajan perfectamente:

  1. Mismo Origen (Same-Origin): Inertia y tu API de Laravel residen en el mismo dominio. Sanctum aprovecha esto usando cookies y la cabecera X-XSRF-TOKEN para gestionar la autenticación y protegerte contra ataques CSRF (Cross-Site Request Forgery). No necesitas tokens de autenticación complejos, lo que simplifica mucho el desarrollo.

  2. Autenticación sin Estado (Stateless Authentication): Sanctum usa un sistema de autenticación ligero basado en cookies de sesión de Laravel. El backend verifica la sesión con cada petición, pero no mantiene el estado de autenticación en la base de datos para la API, lo que lo hace más escalable.

  3. Manejo Automático: Inertia se encarga automáticamente de configurar las cabeceras X-CSRF-TOKEN y las cookies. La mayoría de las veces, no necesitas preocuparte por esto en tus llamados fetch o peticiones axios, ya que Inertia se integra con ellos.


¿Qué necesitas para que funcione?

Básicamente, los pasos que ya te mostré en la respuesta anterior son la base del funcionamiento de Sanctum. Para una aplicación Inertia, el proceso es muy sencillo:

  1. Petición inicial: Cuando el usuario visita la página de tu aplicación, Laravel crea una cookie de sesión con su información.

  2. Llamadas fetch o axios: Cuando tu componente de Inertia hace un llamado a la API, el navegador incluye automáticamente la cookie de sesión.

  3. Protección CSRF: Para proteger las peticiones POST, PUT y DELETE, Laravel espera una cabecera X-XSRF-TOKEN. Si usas axios, puedes configurarlo para que lea el valor de la cookie y lo envíe en las cabeceras de todas las peticiones. En tu caso, si usas fetch, necesitarías implementar la función que te mostré en la respuesta anterior para que lo envíe en cada llamada.


En resumen

Sí, usar Sanctum es la forma más segura y eficiente de autenticar peticiones API desde tu aplicación web de Inertia. Su diseño basado en cookies y cabeceras está perfectamente alineado con la arquitectura de SPA de un solo origen, haciéndola la solución ideal para tu caso.


Artículo externo How to Set Up Sanctum in Laravel

No hay comentarios: