cyu / rack-cors

Rack Middleware for handling Cross-Origin Resource Sharing (CORS), which makes cross-origin AJAX possible.
MIT License
3.26k stars 263 forks source link

the Vary header does not seem to be set for CORS pre-flight checks that use the OPTIONS method #238

Open regisebersole opened 2 years ago

regisebersole commented 2 years ago

Caveat: I may have a misconfiguration.

The vary option only seems to be honored for a specific resource for requests made with HTTP methods other than the pre-flight OPTIONS requests.

Whether or not the vary option is set for specific resource, no vary headers will be sent when the request uses the OPTIONS method.

This seems to cause a problem with Chrome where it caches the response for the period of time that is configured for max_age, which can be a problem for the use-case where Access-Control-Allow-Origin will neither be "*" nor a single static origin: https://fetch.spec.whatwg.org/#cors-protocol-and-http-caches The work-around of course would be to set a very low max_age or to use a configuration option outside of rack-cors to set the vary header(s).

I recognize that this is a very old comment, but I wondered if it's still the design of rack-cors: https://github.com/cyu/rack-cors/issues/73#issuecomment-236168996

To reproduce, I am using rack-cors v1.1.1 and my config.ru is, where I currently have all http origins allowed (contrary to the nature of this issue) but would like to make the origins a conditional depending on a dynamic list of configured OIDC client URLs so that it will not simply be "*":

require 'rack/cors'
use Rack::Cors do
  allow do
    origins /\Ahttp.*/ # this should be a lookup for configured http and https OIDC clients
    resource '/oauth/*', headers: :any, methods: [:get, :post],
      max_age: 600,
      vary: ['Origin']
  end
end

require ::File.expand_path('../config/environment', __FILE__)
run Rails.application

For an example of how Google responds:

curl 'https://openidconnect.googleapis.com/v1/userinfo' \
  -X 'OPTIONS' \
  -H 'authority: openidconnect.googleapis.com' \
  -H 'accept: */*' \
  -H 'accept-language: en-US,en;q=0.9' \
  -H 'access-control-request-headers: authorization,content-type' \
  -H 'access-control-request-method: POST' \
  -H 'cache-control: no-cache' \
  -H 'origin: http://localhost:8080/callback' \
  -H 'pragma: no-cache' \
  -H 'referer: http://localhost:8080/callback' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: same-site' \
  --compressed -v 2>&1

Where Google replies with:

vary: origin
vary: referer
vary: x-origin
access-control-allow-origin: http://localhost:8080/callback
access-control-allow-methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT
access-control-allow-headers: authorization,content-type
access-control-max-age: 3600
istvanp commented 1 year ago

I monkey patched it as such:

module Rack
  class Cors
    class Resource
      def to_preflight_headers(env)
        h = to_headers(env)
        h.merge!('Vary' => vary_headers.join) # <== The patch

        if env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
          h.merge!('Access-Control-Allow-Headers' => env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS])
        end
        h
      end
    end
  end
end