Permitir requisições cross-origin apenas para message bus

Estou tentando implementar um widget de chat que possa ser incorporado em qualquer site e, para isso, decidi usar o MessageBus para comunicação entre o widget e meu backend Rails. Como ele pode ser incorporado em qualquer origem, precisei lidar com requisições cross-origin.

No entanto, quero habilitar o CORS apenas para requisições do message bus e não para todas as minhas outras rotas. Já vi este problema CORS configuration · Issue #135 · discourse/message_bus · GitHub

Foi o que fiz em meu 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, mas isso permite requisições cross-origin para qualquer rota, o que não é o que eu quero.

Também tentei criar minha própria middleware

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

  def call(env)
    # Se for message bus e uma requisição OPTIONS, retorna cabeçalhos CORS
    if env['PATH_INFO'].start_with?('/message-bus') && env['REQUEST_METHOD'] == 'OPTIONS'
      # Aplica cabeçalhos CORS para requisições /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

Mas ainda tenho problemas cross-origin com esta middleware.

Alguma outra ideia?

Outra coisa que seria ótimo é habilitar cross-origin apenas para canais específicos. (No meu caso, os canais do message bus de chat). Eu uso o MessageBus em outras partes da minha aplicação que não fazem requisições cross-origin e gostaria de mantê-lo assim.

2 curtidas

Consegui fazer funcionar usando o segundo método (middleware personalizado para habilitar CORS apenas para requisições do Message Bus). O problema estava em access-control-allow-headers que não estavam configurados da maneira correta. A correção foi popular o cabeçalho da resposta access-control-allow-headers com os cabeçalhos de requisição Access-Control-Request-Headers.

Aqui está a implementação 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)
    # Se for message bus e uma requisição OPTIONS, retorna cabeçalhos CORS
    if env[PATH_INFO].start_with?('/message-bus') && env[REQUEST_METHOD] == OPTIONS && env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
      origin = env['HTTP_ORIGIN']
      # Aplica cabeçalhos CORS para requisições /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

Eu estava tendo problemas para entender o papel do cabeçalho HTTP “Access-Control-Request-Headers”, então perguntei ao ChatGPT:

O cabeçalho HTTP “Access-Control-Request-Headers” é usado no contexto de requisições Cross-Origin Resource Sharing (CORS). CORS é um mecanismo que permite que aplicações web façam requisições para um domínio diferente daquele de onde a aplicação se originou.

Quando uma aplicação web faz uma requisição cross-origin, ela normalmente envia uma requisição inicial “preflight” para o servidor para determinar se a requisição real é permitida. A requisição preflight inclui o cabeçalho “Access-Control-Request-Headers”, que lista os cabeçalhos adicionais que a aplicação deseja incluir na requisição real.

Por exemplo, se uma aplicação web deseja incluir cabeçalhos personalizados como “X-Auth-Token” ou “Authorization” em sua requisição cross-origin, ela especificaria esses cabeçalhos no cabeçalho “Access-Control-Request-Headers” da requisição preflight.

O servidor que recebe a requisição preflight pode então inspecionar o cabeçalho “Access-Control-Request-Headers” para determinar se os cabeçalhos solicitados são permitidos para a requisição real. Se o servidor aprovar os cabeçalhos solicitados, ele responde com o cabeçalho Access-Control-Allow-Headers apropriado na resposta preflight, permitindo que a aplicação web prossiga com a requisição real.

Em resumo, “Access-Control-Request-Headers” é um cabeçalho HTTP usado em requisições preflight CORS para especificar os cabeçalhos adicionais que uma aplicação web deseja incluir em sua requisição cross-origin.

Estou compartilhando isso caso alguém mais esteja tentando fazer a mesma coisa. Também estou aberto a feedback, especialmente em relação a questões de segurança que esta implementação possa abrir.

1 curtida

Você pode adicionar cabeçalhos de resposta extras por os docs:

Em, por exemplo, config/initializers/message_bus.rb:

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

Esqueci de mencionar, mas esta é a primeira coisa que tentei e não funcionou. Mas isso foi antes de eu descobrir sobre o Access-Control-Allow-Headers. Talvez funcione se eu adicioná-los assim.


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

Vou tentar novamente desta forma em vez do middleware personalizado e atualizarei este tópico.

Sem sorte com isso também. Por enquanto, mantive o middleware personalizado.