Para un desarrollador experimentado, este texto puede no ser sorprendente en absoluto, pero creo que muchos de los artículos que he leído sobre la configuración de CORS en Rails decían algo así como: usa rack-cors, permite a cualquier host acceder a la API, y (opcionalmente): deberías considerar algo diferente (que permitir a cualquier host) en producción.
La propuesta código siempre estaba cerca del de abajo:
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
orígenes ''
resource '', cabeceras: :any, métodos: :any
end
end
y, por desgracia, estos textos apenas nos explicaban qué hacer realmente en la producción.
Se me da bastante bien copiar y pegar (A veces bromeo con que las empresas podrían contratar a un copy-paster de Stack Overflow), en cuanto a que hay un momento de "pensar y ajustar" entre "copiar" y "pegar". Por lo tanto, me gustaría elaborar un poco sobre lo que estamos haciendo aquí y cómo funciona en la vida real.
Espero que no te importe que empiece con una breve introducción a la teoría del honor y luego pase a los ejemplos de Rails.
Introducción
Empecemos por el principio. Para explicarlo mejor, he dividido la introducción en tres partes. La primera parte explicará qué es un origen, el término clave para lo que estamos tratando aquí. La segunda trata sobre el SOP, una breve descripción. Y la última parte habla del CORS
sí mismo.
¿Qué es un origen?
Según MDN Web Docs:
- El origen de un contenido web viene definido por el esquema (protocolo), el host (dominio) y el puerto de la URL utilizada para acceder a él. Dos objetos tienen el mismo origen sólo cuando el esquema, el host y el puerto coinciden (fuente)
Parece bastante claro, ¿no? Analicemos dos ejemplos de MDN, por si acaso.
http://example.com/app1/index.html
, http://example.com/app2/index.html
Los 2 anteriores tienen el mismo origen porque:
- sus esquemas (http) son los mismos,
- sus dominios (ejemplo.com) son los mismos,
- sus puertos (implícitos) son los mismos.
http://www.example.com
, http://myapp.example.com
Estos 2 tienen diferente origen porque los dominios (www.example.com
, myapp.example.com
) son diferentes.
Espero que esté suficientemente claro. Si no, por favor vaya a los MDN Web Docs para más ejemplos.
¿Qué es la PNT?
MDN Web Docs dice (fuente):
- La política del mismo origen es un mecanismo de seguridad crítico que restringe cómo un documento o script cargado desde un origen puede interactuar con un recurso de otro origen. Ayuda a aislar documentos potencialmente maliciosos, reduciendo posibles vectores de ataque.
- Normalmente se permiten las escrituras entre orígenes. Algunos ejemplos son los enlaces, las redirecciones y el envío de formularios.
- Normalmente se permite la incrustación entre orígenes.
- Las lecturas cruzadas no suelen estar permitidas, pero el acceso de lectura suele filtrarse por incrustación.
Utilice CORS
para permitir el acceso entre orígenes
Bueno, como puedes ver, hay mucho sobre el comportamiento entre orígenes en las definiciones de SOP. No pasa nada. Todo lo que debemos saber ahora es que el mismo origen tiene más privilegios y podemos flexibilizar las reglas para los orígenes cruzados usando CORS. Y aquí entra la siguiente sección.
¿Qué es CORS?
Basándome en las palabras de MDN:
- Cross-Origin Resource Sharing (CORS) es un mecanismo basado en cabeceras HTTP que permite a un servidor indicar cualquier otro origen (dominio, esquema o puerto) distinto del suyo desde el que un navegador debe permitir la carga de recursos. CORS también se basa en un mecanismo por el cual los navegadores realizan una solicitud de "verificación previa" al servidor que aloja el recurso de origen cruzado, con el fin de comprobar que el servidor permitirá la solicitud real. En esa comprobación previa, el navegador envía cabeceras que indican el método HTTP y las cabeceras que se utilizarán en la solicitud real (fuente).
Eso sigue sin ser suficiente. Lo que no se dijo allí explícitamente es que el encabezado más importante cuando se utiliza CORS
es Access-Control-Allow-Origin
:
- En
Access-Control-Allow-Origin
indica si la respuesta puede compartirse con el código solicitante del origen dado (fuente).
Pues eso debería ser todo. En la vida real, al configurar CORS
normalmente configuramos el ACAO
cabecera primero.
La vida real
Eso es todo en cuanto a definiciones. Volvamos a Rails y a los ejemplos de la vida real.
¿Cómo configurar CORS en Rails?
Sin duda, utilizaremos corchetes de cremallera (como nos han dicho). Recordemos el primer fragmento, el que más a menudo se proporciona en otros artículos:
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
orígenes ''
resource '', cabeceras: :any, métodos: :any
end
end
El número de opciones es enorme o incluso infinito, pero consideremos estas dos:
- estamos construyendo la API que pueden utilizar los clientes de navegadores de terceros,
- tenemos la típica separación frontend/backend y queremos permitir que nuestros clientes de confianza accedan a la API.
API de construcción a la que acceden clientes de terceros
Si te enfrentas a la primera opción, probablemente podrías optar por orígenes
'*' - quieres que otros construyan un cliente sobre tu API, y no sabes quiénes son, ¿verdad?
Separación típica entre frontend y backend
Si está desarrollando esto último, probablemente no quiera que todo el mundo haga peticiones cross-origin a su API. Más bien quieres
- permitir que los clientes de producción accedan a la API de producción,
- lo mismo para la puesta en escena,
- lo mismo para localhost,
- es posible que desee permitir que las aplicaciones de revisión de FE accedan a la puesta en escena.
Seguiremos utilizando corchetes (como nos dijeron), pero a nuestra manera.
Utilicemos 2 variables ENV: ORÍGENES_PERMITIDOS
para las definiciones de origen literales (un asterisco o una URL real) y ORIGEN_PERMITIDO_REGEXPS
para los patrones.
config/initializers/cors.rb
frozenstringliteral: true
toregexp = ->(cadena) { Regexp.new(cadena) }
hosts = [
*ENV.fetch('ALLOWEDORIGINS').split(','),
*ENV.fetch('ALLOWEDORIGINREGEXPS').split(';').map(&to_regexp)
]
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins(*hosts)
recurso '*',
métodos: %i[get post put patch delete opciones head],
cabeceras: :any
fin
fin
¿Qué está pasando aquí?
- Como puedes ver, estamos dividiendo los valores definidos en variables ENV con diferentes separadores. Esto se debe a que es menos probable que aparezca un punto y coma en el patrón de definición de URL.
- Los valores literales están listos para su uso, pero tenemos que mapear los patrones para que sean instancias Regexp reales.
- A continuación, lo unimos todo y permitimos que estos hosts accedan a cualquier recurso con métodos incluidos en nuestra API.
Esto debería darle suficiente flexibilidad para definir los valores adecuados en sus entornos de desarrollo, preparación y producción.
Conclusiones
Resumamos todo lo anterior en puntos clave:
- utilizar variables ENV para configurar
CORS
,
- utilizar expresiones regulares para permitir que distintos orígenes accedan a la API de preparación (por ejemplo, para aplicaciones de revisión),
- Pon siempre "pensar y ajustar" entre "copiar" y "pegar".
Pues eso. ¡Que tengas un buen día! 🙂
Más información:
¿Por qué debería (probablemente) utilizar Typescript?
10 startups de Nueva York dignas de mención en 2021