Permitir solicitudes de origen cruzado solo para el bus de mensajes

Estoy intentando implementar un widget de chat que se pueda incrustar en cualquier sitio web y, para ello, decidí usar MessageBus para comunicarme entre el widget y mi backend de Rails. Dado que se puede incrustar en cualquier origen, necesité lidiar con las solicitudes de origen cruzado.

Sin embargo, quiero habilitar CORS solo para las solicitudes de MessageBus y no para todas mis otras rutas. Ya vi este problema CORS configuration · Issue #135 · discourse/message_bus · GitHub

Esto es lo que hice en mi config/initializers/cors.rb.

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options]
  end
end

Funciona, pero esto permite las solicitudes de origen cruzado para cualquier ruta, que no es lo que quiero.

También intenté crear mi propia middleware

# app/middleware/message_bus_cors_middleware.rb
class MessageBusCorsMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    # Si es message bus y una solicitud OPTIONS, devuelve las cabeceras CORS
    if env['PATH_INFO'].start_with?('/message-bus') && env['REQUEST_METHOD'] == 'OPTIONS'
      # Aplica las cabeceras CORS para las solicitudes /message-bus
      headers = {
        'Access-Control-Allow-Origin' => '*',
        'Access-Control-Allow-Methods' => 'GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD',
        'Access-Control-Allow-Headers' => 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Visitor-Token',
        'Access-Control-Max-Age' => '86400'
      }
      [200, headers, []]
    else
      @app.call(env)
    end
  end
end

Pero todavía tengo problemas de origen cruzado con esta middleware.

¿Alguna otra idea?

Otra cosa que sería genial es habilitar el origen cruzado solo para canales específicos. (En mi caso, los canales de MessageBus de chat). Lo uso en otras partes de mi aplicación que no hacen solicitudes de origen cruzado y me gustaría mantenerlo así.

2 Me gusta

Pude hacerlo funcionar utilizando el segundo método (middleware personalizado para habilitar CORS solo para solicitudes de Message Bus). El problema estaba en los access-control-allow-headers que no estaban configurados de la manera correcta. La solución fue poblar los access-control-allow-headers de la respuesta con los encabezados de solicitud Access-Control-Request-Headers.

Aquí está la implementación final:

class MessageBusCorsMiddleware
  HTTP_ORIGIN = 'HTTP_ORIGIN'
  HTTP_X_ORIGIN = 'HTTP_X_ORIGIN'

  HTTP_ACCESS_CONTROL_REQUEST_METHOD = 'HTTP_ACCESS_CONTROL_REQUEST_METHOD'
  HTTP_ACCESS_CONTROL_REQUEST_HEADERS = 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS'
  OPTIONS = 'OPTIONS'
  REQUEST_METHOD = 'REQUEST_METHOD'
  PATH_INFO      = 'PATH_INFO'

  def initialize(app)
    @app = app
  end

  def call(env)
    # Si es message bus y una solicitud OPTIONS, devuelve las cabeceras CORS
    if env[PATH_INFO].start_with?('/message-bus') && env[REQUEST_METHOD] == OPTIONS && env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
      origin = env['HTTP_ORIGIN']
      # Aplica cabeceras CORS para solicitudes /message-bus
      headers = {
        "access-control-allow-origin" => "*",
        "access-control-allow-methods" => "GET, POST, PUT, PATCH, DELETE, OPTIONS",
        "access-control-expose-headers" => "",
        "access-control-max-age" => "7200"
      }

      headers.merge!('access-control-allow-headers' => env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]) if env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
      [200, headers, []]
    else
      @app.call(env)
    end
  end
end

Tuve problemas para entender el papel del encabezado HTTP “Access-Control-Request-Headers”, así que le pregunté a ChatGPT:

El encabezado HTTP “Access-Control-Request-Headers” se utiliza en el contexto de las solicitudes de Cross-Origin Resource Sharing (CORS). CORS es un mecanismo que permite a las aplicaciones web realizar solicitudes a un dominio diferente al de origen de la aplicación.

Cuando una aplicación web realiza una solicitud de origen cruzado, normalmente envía una solicitud inicial de “preflight” al servidor para determinar si la solicitud real está permitida. La solicitud de preflight incluye el encabezado “Access-Control-Request-Headers”, que enumera los encabezados adicionales que la aplicación desea incluir en la solicitud real.

Por ejemplo, si una aplicación web desea incluir encabezados personalizados como “X-Auth-Token” o “Authorization” en su solicitud de origen cruzado, especificará esos encabezados en el encabezado “Access-Control-Request-Headers” de la solicitud de preflight.

El servidor que recibe la solicitud de preflight puede entonces inspeccionar el encabezado “Access-Control-Request-Headers” para determinar si los encabezados solicitados están permitidos para la solicitud real. Si el servidor aprueba los encabezados solicitados, responde con el encabezado Access-Control-Allow-Headers apropiado en la respuesta de preflight, lo que permite a la aplicación web continuar con la solicitud real.

En resumen, “Access-Control-Request-Headers” es un encabezado HTTP utilizado en las solicitudes de preflight de CORS para especificar los encabezados adicionales que una aplicación web desea incluir en su solicitud de origen cruzado.

Comparto esto por si alguien más está intentando lograr lo mismo. También estoy abierto a comentarios, especialmente en lo que respecta a los problemas de seguridad que esta implementación pueda abrir.

1 me gusta

Puede agregar encabezados de respuesta adicionales por los documentos:

En p. ej. config/initializers/message_bus.rb:

MessageBus.extra_response_headers_lookup do |env|
  [
    ["Access-Control-Allow-Origin", "http://example.com:3000"],
  ]
end
1 me gusta

Olvidé mencionarlo, pero esto es lo primero que intenté y no funcionó. Pero eso fue antes de enterarme de Access-Control-Allow-Headers. Quizás funcione si los agrego de esta manera.


MessageBus.extra_response_headers_lookup do |env|
  [
    ["Access-Control-Allow-Origin", "http://example.com:3000"],
    ["Access-Control-Allow-Headers", env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]]
  ]
end

Lo intentaré de nuevo de esta manera en lugar del middleware personalizado y actualizaré este hilo.

Tampoco tuve suerte con eso. Por ahora me quedé con el middleware personalizado.