Cómo resolver el error de solicitud "Access-Control-Allow-Origin" del lado del cliente con tu propia API de Symfony 3

Este error se encontrará e informará en el lado del cliente cuando alguien solicite con Javascript (AJAX) a un punto final de su proyecto Symfony, que normalmente (pero no necesariamente) es una API. En la mayoría de los casos, este error no se puede resolver en el lado del cliente, ya que el error en realidad es causado por el servidor, que a su vez no es un error sino una "medida de seguridad".

Esta medida de seguridad es la política del mismo origen, esta política establece que un navegador web permite que los scripts contenidos en una primera página web ( www.myweb.com/page1.html) accedan a los datos de una segunda página web ( www.myweb.com/script.js), pero solo si ambas páginas web tienen el mismo origen . El origen se define como una combinación de esquema URI ( http://https://etc.), nombre de host (www.domain.com) y número de puerto (típicamente puerto 80). Eso significa, en pocas palabras, que para crear una solicitud a un website Adebemos enviarla desde el mismo website A, si lo haces desde el website Bentonces se aplicará la política y encontrarás el error en la consola.

Esta política fue de alguna manera redundante, porque, ¿qué pasa si su proyecto necesita compartir información con sitios web de terceros? Para resolver este problema, usamos la especificación CORS en nuestro servidor. El intercambio de recursos de origen cruzado (CORS) es una especificación que permite un acceso verdaderamente abierto a través de los límites del dominio. Entonces, si ofrece contenido público, debe considerar (de alguna manera ... debe hacerlo ) usar CORS para abrirlo para el acceso universal a JavaScript / navegador. Puede leer más sobre CORS aquí .

Si ejecuta XMLHttpRequest desde el navegador a un punto final de su aplicación ( https://sandbox/api) con Javascript desde otro sitio web ( https://fiddle.jshell.net) usando el siguiente código:

$.getJSON("https://sandbox/api", function(data){
	console.log(data);
});

Recibirá el siguiente mensaje de error en la consola:

Normalmente, en PHP, puede habilitar CORS en su script implementando el siguiente encabezado:

<?php
header("Access-Control-Allow-Origin: *");

El * significa que todos los dominios se les permite acceder a la respuesta de nuestro script en el servidor. Puede establecer como valor solo 1 dominio; de lo contrario, creará más problemas para usted más adelante, además, si necesita agregar soporte para múltiples dominios, verifique esta pregunta en Stack Overflow .

Sin embargo, como estás usando Symfony, no lo vas a hacer. En su lugar, debes modificar la respuesta devuelta en el controlador.

Resolver con respuestas en controladores

Usando el modelo de controlador, en este ejemplo vamos a usar un controlador simple que genera el error (no tiene encabezados) y devuelve una respuesta JSON simple:

<?php

namespace sandbox\mainBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    /**
     * El punto de acceso a esta url será:
     * https://sandbox/api
     */
    public function apiAction(){
        $response = new Response();
        
        $date = new \DateTime();

        $response->setContent(json_encode([
            'id' => uniqid(),
            'time' => $date->format("Y-m-d")
        ]));

        $response->headers->set('Content-Type', 'application/json');
       
        return $response;
    }
}

Para solucionarlo, necesitamos modificar la respuesta y agregar el encabezado Access-Control-Allow-Origin:

<?php

public function apiAction(){
    $response = new Response();
    $date = new \DateTime();

    $response->setContent(json_encode([
        'id' => uniqid(),
        'time' => $date->format("Y-m-d")
    ]));

    $response->headers->set('Content-Type', 'application/json');
    // Permitir todos los sitios web
    $response->headers->set('Access-Control-Allow-Origin', '*');
    // O un sitio web predefinido
    //$response->headers->set('Access-Control-Allow-Origin', 'https://jsfiddle.net/');
    // También puedes configurar los métodos permitidos, si lo desea
    //$response->headers->set('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, PATCH, OPTIONS');
    
    return $response;
}

El parámetro de origen especifica un URI que puede acceder al recurso. El navegador debe hacer cumplir esto. Para solicitudes sin credenciales, el servidor puede especificar " *" como comodín, permitiendo así que cualquier origen acceda al recurso.

Resolver con archivos estáticos y API ya implementada

Pero, ¿qué pasa si maneja archivos estáticos en su lugar o tiene una API enorme ya construida? Por ejemplo:

1) Con archivos: si tienes un archivo ( myfile.txt) en el directorio web (en la carpeta de recursos) de tu proyecto Symfony (en el dominio A ) y quieres solicitar ese archivo del dominio B con AJAX:

$.get("https://sandbox/resources/myfile.txt", function(data){
    console.log(data);
});

2) Con una API ya construida: imaginemos que ya ha creado una API Restful usando FOSRestBundle :

<?php

namespace AppBundle\Controller;

class UsersController
{
    public function copyUserAction($id) // RFC-2518
    {} // "copy_user"            [COPY] /users/{id}

    public function propfindUserPropsAction($id, $property) // RFC-2518
    {} // "propfind_user_props"  [PROPFIND] /users/{id}/props/{property}

    public function proppatchUserPropsAction($id, $property) // RFC-2518
    {} // "proppatch_user_props" [PROPPATCH] /users/{id}/props/{property}

    // Y MUCHAS FUNCIONES MÁS :(
}

Encontrará el mismo error "XMLHttpRequest no se puede cargar" en la consola en ambos casos, por lo que deberá agregar el encabezado mencionado en cada respuesta. Sin embargo, modificar la respuesta en cada controlador o incluso devolver los archivos con PHP puro en lugar de ngix sería contraproducente y muy ineficiente . Por tanto, para hacerlo de la forma correcta y sencilla vamos a depender del NelmioCorsBundle . NelmioCorsBundle le permite enviar encabezados de intercambio de recursos de origen cruzado con una configuración por URL al estilo de ACL.

Para instalar NelmioCorsBundle, ejecute el siguiente comando en el compositor:

composer require nelmio/cors-bundle

O agregue la siguiente línea en su archivo composer.json y luego ejecuta composer install:

{
    "require": {
        "nelmio/cors-bundle": "^1.4"
    }
}

Luego proceda a registrar el paquete en el archivo AppKernel ( app/AppKernel.php) en el método registerBundles:

<?php

public function registerBundles()
{
    $bundles = [
        ///..///
        new Nelmio\CorsBundle\NelmioCorsBundle(),
        ///..///
    ];

    ///..///
}

Y finalmente proceda a configurar la configuración requerida para que su proyecto funcione ( lea más sobre el NelmioCorsBundle en el repositorio oficial en Github aquí ).

Según sus necesidades y requisitos de su proyecto, es posible que deba leer la documentación del paquete para ver qué opciones necesita habilitar y modificar. Sin embargo, la siguiente configuración en el config.ymlarchivo debería hacer el truco para que el /apipunto final (y todas las sub-URL [ api/somethingapi/other-endpoint]) estén disponibles para el acceso desde otros dominios:

nelmio_cors:
        defaults:
            allow_credentials: false
            allow_origin: []
            allow_headers: []
            allow_methods: []
            expose_headers: []
            max_age: 0
            hosts: []
            origin_regex: false
        paths:
            '^/api':
                allow_origin: ['*']
                allow_headers: ['*']
                allow_methods: ['POST', 'PUT', 'GET', 'DELETE']
                max_age: 3600

La /api puede acceder al punto final desde cualquier dominio y permitir cualquier tipo de encabezado, es posible que desee filtrar esto en su proyecto. ¡No olvide borrar el caché antes de realizar la prueba y estará listo para comenzar!

Resuélvelo en el lado del cliente

Si recibe este error mientras intenta acceder a una API de terceros (y probablemente no lo resolverán por un tiempo), puede confiar en un método poco ortodoxo para recuperar los datos de la API con Javascript fácilmente usando el servicio gratuito cors-anywhere. Lea más sobre cómo puede omitir la misma política de origen con XMLHttpRequest en este artículo .

Que te diviertas ❤️!

Esto podria interesarte

Conviertete en un programador más sociable