Aprende a configurar las CORS en Drupal

Imagen de servidores

Las CORS suelen ser algo a lo que la gente no le presta atención, pero son una parte muy importante de la seguridad de la web, y aunque en un proyecto normal no es necesario configurarlas, en algunos casos concretos sí que las necesitamos.

Realmente son muy fáciles de configurar ya que únicamente es necesario modificar un archivo, y la configuración se hace modificando valores en un array. Pero antes de comenzar, deberíamos saber un poquito que son las CORS y porque son importantes.

¿Que son las CORS?

Son un sistema de seguridad que nos permite añadir una capa extra de protección a nuestro sitio frente a peticiones externas o poniendo requisitos especiales. Por ejemplo, podemos limitar desde que dominios se hacen las peticiones, los métodos, ya sean GET, POST, PATCH... también en base a unas condiciones en las cabeceras de la petición... disponemos de varias opciones.

Un ejemplo podría ser requerir una cabecera inventada por nosotros, la cual es requerida para poder acceder a unos endpoints, o que desde un dominio externo no puedan acceder a nuestras APIs.

¿Porque son necesarias?

Configurándolas correctamente podemos impedir o controlar los accesos a nuestro proyecto o específicamente a unas URL concretas. En la mayoría de los casos no son necesarias, pero si por ejemplo disponemos de un proyecto el cual dispone de unos endpoint, y queremos que solo sean accesibles desde un dominio concreto, necesitamos las CORS para impedir que sitios externos usen esos endpoints.

Configurando las CORS

Si necesitamos aplicarlas a nivel global, son muy sencillas de configurar, únicamente tenemos que abrir el archivo web/sites/default/default.services.yml y en la parte inferior veremos una sección como la siguiente:

  # Configure Cross-Site HTTP requests (CORS).
  # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
  # for more information about the topic in general.
  # Note: By default the configuration is disabled.
  cors.config:
    enabled: false
    # Specify allowed headers, like 'x-allowed-header'.
    allowedHeaders: []
    # Specify allowed request methods, specify ['*'] to allow all posible ones.
    allowedMethods: []
    # Configure requests allowed from specific origins. Do not include trailing
    # slashes with URLs.
    allowedOrigins: ['*']
    # Sets the Access-Control-Expose-Headers header.
    exposedHeaders: false
    # Sets the Access-Control-Max-Age header.
    maxAge: false
    # Sets the Access-Control-Allow-Credentials header.
    supportsCredentials: false

Eso que vemos en la configuracion de ejemplo de las CORS, no tenemos que modificarlo aquí, lo que tenemos que hacer es copiar ese codigo yml y pegarlo en el archivo web/sites/default/services.yml

Una vez tenemos ahí la configuración, lo primero es activarla, para ello cambiamos "enabled: false" por "enabled: true", tambien podemos añadir las cabeceras que necesitemos a "allowedHeaders", que es un array, también los métodos permitidos en "allowedMethods" y los dominios desde los cuales se permite el acceso en "allowedOrigins".

Vemos un ejemplo.

  # Configure Cross-Site HTTP requests (CORS).
  # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
  # for more information about the topic in general.
  # Note: By default the configuration is disabled.
  cors.config:
    enabled: true
    # Specify allowed headers, like 'x-allowed-header'.
    allowedHeaders: ['X-Access-escueladrupal']
    # Specify allowed request methods, specify ['*'] to allow all posible ones.
    allowedMethods: ['GET']
    # Configure requests allowed from specific origins. Do not include trailing
    # slashes with URLs.
    allowedOrigins: ['escueladrupal.com', 'localhost:3000']
    # Sets the Access-Control-Expose-Headers header.
    exposedHeaders: false
    # Sets the Access-Control-Max-Age header.
    maxAge: false
    # Sets the Access-Control-Allow-Credentials header.
    supportsCredentials: false

En ese ejemplo, vemos como las CORS están activadas, será necesario que se incluya la cabecera "X-Access-escueladrupal" (el cual me he inventado), además solamente se permitirán las peticiones GET y desde el dominio de producción "escueladrupal.com" y desde el entorno de desarrollo que podria ser "localhost:3000".

Si alguien trata de acceder o hacer una petición a mi proyecto, debe cumplir con todos los requisitos, incluir la cabecera que se indica, usar el método GET y un dominio permitido.

Como desactivar las CORS

Desactivarlas es muy sencillo, tenemos que irnos al archivo donde las hayamos configurado (en este ejemplo web/sites/default/services.yml) y cambiar la opción "enabled: true" por "enabled: false", y con eso ya tendremos las CORS desactivadas.

CORS personalizadas por URL

Si llegamos a este punto es que necesitamos unas CORS que funcionen únicamente en algunas URL específicas, el problema aquí radica en que la configuración de las CORS en Drupal es para todo el sitio. No nos permite indicar en que URL se aplica y en cuáles no.

Lo mejor que podemos hacer en este caso es desactivar las CORS y desarrollar nosotros mismos nuestra implementación, para ello vamos a crear un middleware y comprobar en que URL nos encontramos antes de comprobar las CORS.

Primero nos creamos el servicio que actuará como middleware, nos vamos al archivo services.yml de nuestro modulo y añadimos lo siguiente:

  escueladrupal.cors:
   class: Drupal\escueladrupal\Middleware\EscuelaCorsMiddleware
   tags:
     - { name: http_middleware, priority: 250 }

Ahora tenemos que crear la clase EscuelaCorsMiddleware, en este ejemplo yo lo voy a crear aquí: web/modules/custom/escueladrupal/src/Middleware/EscuelaCorsMiddleware.php

Y tendrá el siguiente código php:

namespace Drupal\escueladrupal\Middleware;

use Asm89\Stack\CorsService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
    
class EscuelaCorsMiddleware implements HttpKernelInterface {
    
/**
 * @var \Symfony\Component\HttpKernel\HttpKernelInterface
 */
protected $app;
      
/**
 * @var \Asm89\Stack\CorsService
 */
protected $cors;
      
/**
 * @var array
 */
protected $defaultOptions = [
  'allowedHeaders' => [],
  'allowedMethods' => [],
  'allowedOrigins' => [],
  'allowedOriginsPatterns' => [],
  'exposedHeaders' => false,
  'maxAge' => false,
  'supportsCredentials' => false,
];
      
/**
 * @param HttpKernelInterface $app
 */
public function __construct(HttpKernelInterface $app) {
  $this->app = $app;
  $this->cors = new CorsService($this->defaultOptions);
}
      
/**
 * {@inheritdoc}
 */
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true){
      
    $url = $request->getPathInfo();
        
    if (!str_starts_with($url, '/escuela/')) {
      return $this->app->handle($request, $type, $catch);
    }
        
    if ($this->cors->isPreflightRequest($request)) {
      return $this->cors->handlePreflightRequest($request);
    }
        
    if (!$this->cors->isActualRequestAllowed($request)) {
      return new Response('Not allowed.', 403);
    }
        
    $response = $this->app->handle($request, $type, $catch);
        
    return $this->cors->addActualRequestHeaders($response, $request);
  }
}
    

 Y listo, en ese ejemplo estamos añadiendo unas CORS personalizadas a todas las url que comiencen con "/escuela/", y las opciones de las CORS antes las configurábamos en un yml, pero ahora están en el atributo de la clase $defaultOptions.

Comparte este artículo:
Publicado por Borja
Image

Me metí en la aventura de Drupal con la versión 6, y aquí estoy, 10 años después, escribiendo articulos y haciendo videos sobre Drupal, quien me lo iba a decir. Aunque he probado otros framworks y cms, me quedo con Drupal de lejos, pero Symfony y Django estan entre mis favoritos. Aficionado a la montaña, la bicicleta, y el comer, de eso que no falte.