Allow cross origin requests only for message bus

I am trying to implement a chat widget that can be embedded in any website and for that I decided to use MessageBus to communicate between the widget and my Rails backend. Since it can be embedded in any origin I needed to deal with cross origin requests.

However I want to enable CORS only for message bus requests and not all my other routes. I already saw this issue CORS configuration · Issue #135 · discourse/message_bus · GitHub

That’s what I did in my 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

It works but this allows the cross origin requests for any route which is not what I want.

I also tried to do my own middleware

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

  def call(env)
    # If it's message bus and an OPTIONS request, return CORS headers
    if env['PATH_INFO'].start_with?('/message-bus') && env['REQUEST_METHOD'] == 'OPTIONS'
      # Apply CORS headers for /message-bus requests
      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

But I still have cross origin issues with this middleware.

Any other idea ?

Another thing that would be great is to enable cross origin only for specific channels. ( In my case the chat message bus channels). In did I use MessageBus in other parts of my application that do not do cross origin requests and I would like to keep it like that.

2 Likes

I was able to make it work using the second method (Custom middleware to enable cors only for Message Bus requests). The issue was on the access-control-allow-headers that were not set the right way. The fix was to populate the response access-control-allow-headers with the Access-Control-Request-Headers request headers.

Here is the final implementation:

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)
    # If it's message bus and an OPTIONS request, return CORS headers
    if env[PATH_INFO].start_with?('/message-bus') && env[REQUEST_METHOD] == OPTIONS && env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
      origin = env['HTTP_ORIGIN']
      # Apply CORS headers for /message-bus requests
      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

I was having issues understanding the role of the the HTTP header “Access-Control-Request-Headers” so I asked ChatGPT:

The HTTP header “Access-Control-Request-Headers” is used in the context of Cross-Origin Resource Sharing (CORS) requests. CORS is a mechanism that allows web applications to make requests to a different domain than the one the application originated from.

When a web application makes a cross-origin request, it typically sends an initial “preflight” request to the server to determine if the actual request is allowed. The preflight request includes the “Access-Control-Request-Headers” header, which lists the additional headers that the application wants to include in the actual request.

For example, if a web application wants to include custom headers like “X-Auth-Token” or “Authorization” in its cross-origin request, it would specify those headers in the “Access-Control-Request-Headers” header of the preflight request.

The server receiving the preflight request can then inspect the “Access-Control-Request-Headers” header to determine whether the requested headers are allowed for the actual request. If the server approves the requested headers, it responds with the appropriate Access-Control-Allow-Headers header in the preflight response, allowing the web application to proceed with the actual request.

In summary, “Access-Control-Request-Headers” is an HTTP header used in CORS preflight requests to specify the additional headers that a web application wants to include in its cross-origin request.

I am sharing this in case someone else is trying to achieve the same thing. I am also open to feedback especially regarding security issues that this implementation can open.

1 Like

You can add extra response headers per the docs:

In e.g. config/initializers/message_bus.rb:

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

I forgot to mention but this is the first thing I tried and it didn’t work. But that was before I found out about the Access-Control-Allow-Headers. Maybe it will work if I add them like this.


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

I will try again this way instead of the custom middleware and update this thread.

No luck with that as well. For now I kept the custom middleware.