domingo, 15 de junio de 2025

Laravel Sanctum y Axios o Fetch: CORS y Access-Control-* headers

¿Qué relación hay entre CORS y Access-Control-Allow-Credentials?


 La relación entre CORS (Cross-Origin Resource Sharing) y el encabezado Access-Control-Allow-Credentials es fundamental cuando tu aplicación frontend (por ejemplo, una SPA en frontend.com) necesita hacer solicitudes a un backend (API en api.com) y en esas solicitudes deben incluirse credenciales.

Aquí te explico la relación:

CORS (Cross-Origin Resource Sharing)

CORS es un mecanismo de seguridad implementado en los navegadores web que permite a un servidor indicar cualquier origen (dominio, esquema o puerto) diferente al suyo desde el cual un navegador debe permitir la carga de recursos. Es una relajación controlada de la "Same-Origin Policy" (Política del Mismo Origen), que por defecto prohíbe que scripts en una página web accedan a recursos de un origen diferente al de la página.

Cuando un frontend en origin-A.com intenta hacer una solicitud (ej. fetch o XMLHttpRequest) a un recurso en origin-B.com, el navegador aplica la política del mismo origen. Para permitir esta comunicación "cross-origin", origin-B.com debe enviar ciertos encabezados CORS en la respuesta que le digan al navegador que está permitido.

Los encabezados CORS clave incluyen:

  • Access-Control-Allow-Origin: Indica qué orígenes tienen permiso para acceder al recurso.
  • Access-Control-Allow-Methods: Indica qué métodos HTTP están permitidos (GET, POST, PUT, DELETE, etc.).
  • Access-Control-Allow-Headers: Indica qué encabezados de solicitud personalizados están permitidos.
  • Access-Control-Max-Age: Indica por cuánto tiempo se puede almacenar en caché la respuesta preflight.

Access-Control-Allow-Credentials

Este es un encabezado de respuesta HTTP que el servidor envía para indicar si el navegador debe incluir "credenciales" en una solicitud de origen cruzado.

¿Qué se considera "credenciales" en este contexto?

Las credenciales incluyen:

  • Cookies: Las cookies de sesión, cookies de autenticación, etc.
  • Encabezados de Autorización: Como los encabezados Authorization: Bearer <token> o Authorization: Basic <base64-encoded-credentials>.
  • Certificados de cliente TLS.

La Relación: Un Contrato entre Cliente y Servidor

La relación entre CORS y Access-Control-Allow-Credentials es un contrato bidireccional entre el navegador (cliente) y el servidor:

  1. Petición del Cliente (withCredentials = true):

    Para que el navegador siquiera intente enviar credenciales en una solicitud cross-origin, el código JavaScript del cliente (frontend) debe indicarlo explícitamente. Esto se hace configurando la propiedad withCredentials a true en la solicitud:

    • Para fetch(): fetch(url, { credentials: 'include' })
    • Para XMLHttpRequest: xhr.withCredentials = true; Por defecto, los navegadores no envían cookies ni encabezados de autenticación en solicitudes cross-origin para protegerse contra ataques CSRF (Cross-Site Request Forgery) o para evitar la fuga de información sensible.
  2. Respuesta del Servidor (Access-Control-Allow-Credentials: true):

    Si el servidor (tu API de Laravel) recibe una solicitud con withCredentials = true y está dispuesto a permitir que esas credenciales se incluyan en la comunicación de origen cruzado, DEBE responder con el encabezado:

    Access-Control-Allow-Credentials: true
    
    • Si el servidor responde con Access-Control-Allow-Credentials: false (o no lo incluye), y el cliente envió withCredentials = true, el navegador ignora la respuesta del servidor y reporta un error de CORS en la consola, bloqueando el acceso del frontend a la respuesta. Esto es una medida de seguridad.
    • Restricción importante: Si Access-Control-Allow-Credentials es true, el encabezado Access-Control-Allow-Origin NO PUEDE ser * (el wildcard). Debe especificar un origen exacto (o la lista de orígenes permitidos). Esto es una medida de seguridad para evitar que cualquier sitio pueda recibir tus credenciales.

Escenarios Comunes y su Impacto:

  • API Token Authentication (ej. Laravel Sanctum con tokens para móviles/3rd party):

    • Generalmente, no se usa Access-Control-Allow-Credentials: true porque los tokens se envían manualmente en el encabezado Authorization: Bearer .... Si bien el encabezado Authorization se considera una credencial, el cliente no necesita que el navegador maneje automáticamente cookies u otros estados de sesión. Solo necesita Access-Control-Allow-Headers para permitir el encabezado Authorization.
    • Sin embargo, si tu sistema de tokens se basa en alguna cookie (lo cual no es el estándar para JWT o tokens de portador típicos), entonces allow-credentials sí sería relevante.
  • SPA Authentication (ej. Laravel Sanctum con cookies para First-Party Domains):

    • Este es el escenario principal para Access-Control-Allow-Credentials: true. Si tu SPA está en un subdominio (app.example.com) y tu API en otro (api.example.com), y quieres que la SPA se autentique usando las cookies de sesión de Laravel, entonces:
      • Tu SPA debe hacer solicitudes con credentials: 'include'.
      • Tu API debe responder con Access-Control-Allow-Origin: https://app.example.com (no *) y Access-Control-Allow-Credentials: true.
      • Esto le dice al navegador que está bien enviar y recibir cookies de sesión a través de este origen cruzado específico.

En resumen:

Access-Control-Allow-Credentials es un componente específico de CORS que permite la transmisión segura de credenciales (como cookies y encabezados de autenticación) en solicitudes de origen cruzado. Es una "luz verde" explícita del servidor, que debe ser solicitada por el cliente (withCredentials = true) y confirmada por el servidor, siempre que el Access-Control-Allow-Origin no sea un wildcard (*) para mantener la seguridad. Sin esta cabecera, los navegadores se niegan a enviar credenciales en solicitudes de origen cruzado, incluso si el desarrollador del frontend lo solicita.

No hay comentarios: