Guía completa para resolver el problema de no enviar cookies en solicitudes Cross-Origin (CORS)

Si te has encontrado con un escenario en el que el servidor establece correctamente la cookie (visible en la respuesta del navegador), pero en solicitudes posteriores el navegador no devuelve esa cookie al servidor, esta guía es para ti. Este problema casi siempre ocurre cuando el frontend y el backend de tu proyecto están en dos dominios o puertos diferentes — por ejemplo, el frontend en app.example.com y la API en api.example.com.

¿Por qué ocurre esto?

Los navegadores modernos imponen dos políticas de seguridad estrictas para prevenir ataques CSRF y el secuestro de sesiones:

  1. Política CORS: Por defecto, el navegador no permite que las solicitudes entre dominios incluyan credenciales (como cookies) a menos que el servidor lo permita explícitamente.
  2. Política SameSite: A partir de Chrome 80, las cookies se tratan por defecto con el comportamiento SameSite=Lax, lo que significa que no se enviarán en solicitudes entre dominios a menos que se definan explícitamente con SameSite=None.

El punto importante es que para resolver este problema, tanto el lado del cliente como el del servidor deben configurarse correctamente. Modificar solo un lado no es suficiente.

Paso 1: Configuración correcta del servidor

Tres reglas de oro en el lado del servidor

Regla 1 — Activar credenciales: El encabezado de respuesta del servidor debe incluir el siguiente valor:

Access-Control-Allow-Credentials: true

Regla 2 — Sin comodines: Cuando las credenciales están activadas, no puedes usar el carácter comodín * en el encabezado Access-Control-Allow-Origin. Debes especificar exactamente la dirección de dominio completa del frontend:

Access-Control-Allow-Origin: https://app.example.com

Regla 3 — Atributos de la cookie: La cookie debe establecerse con dos atributos: SameSite=None y Secure. Sin estos dos atributos, el navegador almacenará la cookie pero nunca la enviará en solicitudes entre dominios.

Código de ejemplo en Node.js (Express)

const express = require('express');
const cors = require('cors');

const app = express();

app.use(cors({
  origin: 'https://app.example.com', // Dominio exacto del frontend, sin /
  credentials: true
}));

app.post('/login', (req, res) => {
  res.cookie('session_token', 'abc123', {
    httpOnly: true,   // Evita el acceso de JavaScript a la cookie
    secure: true,     // Enviado solo a través de HTTPS
    sameSite: 'none', // Permite el envío en solicitudes Cross-Origin
    maxAge: 24 * 60 * 60 * 1000
  });
  res.json({ message: 'Inicio de sesión exitoso' });
});

Código de ejemplo en PHP

<?php
// Especifica el dominio exacto del frontend, no *
header("Access-Control-Allow-Origin: https://app.example.com");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");

// Responder a la solicitud Preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

// Establecer la cookie con los atributos requeridos
setcookie('session_token', 'abc123', [
    'expires'  => time() + 86400,
    'path'     => '/',
    'secure'   => true,      // Obligatorio
    'httponly' => true,
    'samesite' => 'None'     // Obligatorio para Cross-Origin
]);

Nota: Si estás usando Laravel, establece supports_credentials en true en el archivo config/cors.php y agrega el dominio del frontend al array allowed_origins.

Paso 2: Configuración correcta del cliente (frontend)

Incluso con una configuración completa del servidor, el navegador aún no enviará cookies a menos que declares explícitamente en el lado del cliente que esta solicitud es "con credenciales".

Usando Fetch API

fetch('https://api.example.com/user/profile', {
  method: 'GET',
  credentials: 'include'  // Esta línea es la clave para resolver el problema
})
  .then(res => res.json())
  .then(data => console.log(data));

Usando Axios

// Para una solicitud específica
axios.get('https://api.example.com/user/profile', {
  withCredentials: true
});

// O globalmente para todo el proyecto
axios.defaults.withCredentials = true;

Lista de verificación para solución de problemas

Si el problema persiste después de aplicar lo anterior, verifica estos elementos en orden:

  • ¿Ambos lados están en HTTPS? El atributo Secure significa que la cookie solo funciona en conexiones cifradas. Si tu sitio está en HTTP, el navegador no aceptará la cookie. (Excepción: entorno de desarrollo en localhost)
  • ¿La dirección Origin es exacta? La dirección Access-Control-Allow-Origin debe coincidir exactamente con la URL que ves en el navegador. Incluso una diferencia en www o una barra final / causará un error.
  • ¿La solicitud OPTIONS es exitosa? En la pestaña Network de las herramientas de desarrollo del navegador, verifica la solicitud Preflight (con el método OPTIONS). Esta solicitud debe responderse con el código 200 o 204 y los encabezados CORS correctos.
  • ¿La cookie está almacenada? En el navegador, ve a DevTools ← Application ← Cookies y asegúrate de que la cookie esté almacenada con los atributos Secure y SameSite=None.
  • ¿Tienes un proxy o firewall intermedio? A veces Cloudflare o servidores web como Nginx reescriben los encabezados. Verifica también su configuración.

Resumen

El envío de cookies en solicitudes entre dominios requiere la coordinación de tres factores: el servidor con Access-Control-Allow-Credentials: true y Origin exacto, la cookie con atributos SameSite=None; Secure y el cliente con activación de credentials: 'include' o withCredentials: true. Siguiendo estos tres puntos, el problema se resolverá por completo.


Infraestructura confiable, tranquilidad para los desarrolladores

La implementación correcta de CORS y la gestión de cookies es solo una parte de la historia; la infraestructura en la que se ejecuta tu proyecto también juega un papel decisivo en la estabilidad y seguridad del servicio:

Radib Hosting — Alojamiento web de alta velocidad con soporte completo para SSL gratuito y configuración estándar de encabezados; una opción ideal para alojar el frontend y backend de tus proyectos.

Radib Virtual Server — Si tu proyecto requiere acceso completo, recursos dedicados y libertad en la configuración del servidor web (Nginx/Apache), los servidores virtuales Radib con hardware potente y red estable son la mejor opción para API profesionales.

Radib Debugging and Security Services — Si estás lidiando con errores complejos de CORS, problemas de autenticación o desafíos de seguridad, el equipo técnico de Radib, con una profunda experiencia en resolución de problemas y seguridad de aplicaciones web, está aquí para ayudarte a resolver el problema desde la raíz.

 

¿Fue útil la respuesta? 112 Los Usuarios han Encontrado Esto Útil (112 Votos)