Ir al contenido principal
Laravel, shipping fast.

CORS controla qué dominios pueden llamar a tu API. Esto impide que sitios web cualesquiera accedan a ella sin permiso.

Cuando un navegador hace una petición a tu API desde otro dominio, primero envía una petición «de comprobación previa» con el método OPTIONS para preguntar: «¿está permitido esto?». Sólo si tu API responde con las cabeceras correctas permite el navegador la petición real.

// config/cors.php
return [
    'paths' => ['api/*'], // Apply to API routes only
    'allowed_methods' => ['*'], // GET, POST, DELETE, etc.
    'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')),
    'allowed_headers' => ['*'], // Accept any headers from clients
    'exposed_headers' => [
        'X-RateLimit-Limit',
        'X-RateLimit-Remaining',
        'X-RateLimit-Reset',
    ],
    'max_age' => 0, // Don't cache preflight (reevaluate every request)
    'supports_credentials' => false, // Don't allow cookies/credentials
];

Notas de seguridad importantes:

  • allowed_origins debe ser explícito. Nunca permitas * si tu API maneja datos sensibles.
  • supports_credentials por defecto es false. Si alguna vez lo activas para permitir cookies, ten muchísimo cuidado: abre vulnerabilidades CSRF. Para APIs con tokens Bearer, déjalo en false.
  • La validación del lado del cliente no es seguridad. Un usuario malicioso puede saltarse CORS usando curl o Postman. CORS sólo impide que los navegadores accedan a los datos. Nunca dependas de CORS por sí solo: valida siempre en el servidor.

HeaderFactory: cabeceras consistentes

Cada respuesta que envía tu API debería incluir ciertas cabeceras estándar: de seguridad, de caché, de límite de frecuencia. En lugar de esparcir esa lógica por tu código, centralízala en HeaderFactory.

// app/Factories/HeaderFactory.php
final readonly class HeaderFactory
{
    public static function default(): array
    {
        return [
            'Content-Type' => 'application/json',
            'X-Content-Type-Options' => 'nosniff',
            'X-Frame-Options' => 'DENY',
            'X-XSS-Protection' => '1; mode=block',
            'Referrer-Policy' => 'strict-origin-when-cross-origin',
            'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()',
            'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains',
        ];
    }

    public static function errors(): array
    {
        return array_merge(self::default(), [
            'Cache-Control' => 'no-cache, no-store, must-revalidate',
            'Pragma' => 'no-cache',
            'Expires' => '0',
        ]);
    }
}

¿Por qué centralizar las cabeceras? Porque son críticas para la seguridad. Le dicen al navegador «no adivines el tipo de contenido», «no me incrustes en un marco», «usa HTTPS». Si las escribes a fuego en varios sitios, arriesgas inconsistencia. Centralizar significa: todas las respuestas reciben las mismas cabeceras de seguridad, los cambios se propagan, las pruebas se concentran en un sitio y las cabeceras nuevas se añaden sin tocar los wrappers.