Skip to content

RFC: CORS #319

@dotansimha

Description

@dotansimha

Mostly all of the gateways act same for CORS. The defaults change but usually the options and behavior are the same.
I'll give the details about CORS in Hive Gateway section. Then talk less about technical details about CORS in other gateway sections but only mention about differences more.

Hive Gateway JS & Introduction to CORS

Reference
By default, Hive Gateway allows all origins with Access-Control-Allow-Origin: * when preflight requests are made.
Preflight request is the request sent by the client(usually the browser) with OPTIONS HTTP Method and the information about the client such as the list of the headers about to send, the method is about to be used, and the origin that the client has.
Then the server sends the headers contains usually partial information about its needs to let the client know if the following request will be accepted or not.

You can have either static options or dynamic options based on the request in Hive Gateway.
When a preflight request made, Hive Gateway sends CORS headers based on the configuration given below.

  cors: {
    origin: ['http://localhost:4000', 'http://localhost:3000], // This can be either a string or an array of strings
    credentials: true, // This allows users to benefit from cookies
    allowedHeaders: ['X-Custom-Header'], // This sets the allowed request headers
    methods: ['POST'] // This sets the allowed methods
  }

Keep on mind that, the server should never expose the list of all possible origins but the origin, method etc themselves only when they are matching, otherwise the information given by the server should be empty so the client will know it will be rejected.

Let's say you have the configuration above, and your origin is http://otherhost:3000, then Access-Control-Allow-Origin will be empty, it WON'T HAVE http://localhost:4000 or http://localhost:3000 for security reasons.
Same for other options like headers and methods.
In case of a valid request like let's say we have http://localhost:3000 origin, then it will only return http://localhost:3000 itself not :4000 one.

This is important to know because the dynamic configuration relies on that.

In Hive Gateway, you can provide a dynamic configuration.
So you can see how you can configure the CORS dynamically based on the request

  cors: request => {
    const url = new URL(request.url);
    if (url.origin === 'http://localhost:3000') {
      return {
        origin: url.origin // Allow this
        credentials: false, // Do not allow cookies for this origin
        allowedHeaders: ['X-3000-Header'] // Allow this custom header only for this origin
        methods: ['GET'] // Only allow GET for this origin
      }
    } else if (url.origin === 'http://localhost:3000') {
      return {
        origin: url.origin // Allow this
        credentials: true, // Allow cookies for this origin
        allowedHeaders: ['X-4000-Header'] // Allow this custom header only for this origin
        methods: ['POST'] // Only allow POSTfor this origin
      }
    } else if (url.origin === 'http://localhost:5000') {
       return {
         origin: [] // Do not allow this origin
      }
    }
  }

Apollo Router

It has a similar approach but in a declarative way by using regexp pattern matching etc.
So you can either provide a global CORS option that covers all allowed origins, or you can provide specific CORS rules for specific origins that are matching the given pattern.

Reference

#
# CORS (Cross Origin Resource Sharing)
#
cors:

  # Set to true to allow any origin
  allow_any_origin: false

  # List of accepted origins
  # (Ignored if allow_any_origin is set to true)
  #
  # An origin is a combination of scheme, hostname and port.
  # It does not have any path section, so no trailing slash.
  policies:
   - origins:
        - https://www.your-app.example.com
        - https://studio.apollographql.com # Keep this so GraphOS Studio can run queries against your router
      match_origins:
        - "^https://([a-z0-9]+[.])*api[.]example[.]com$" # any host that uses https and ends with .api.example.com
    # You can provide all options like methods etc here that will be applied only matching origins.

  # Set to true to add the `Access-Control-Allow-Credentials` header
  allow_credentials: false

  # The headers to allow.
  # Not setting this mirrors a client's received `access-control-request-headers`
  # This is equivalent to allowing any headers,
  # except it will also work if allow_credentials is set to true
  allow_headers: []

  # Allowed request methods
  methods:
    - GET
    - POST
    - OPTIONS

  # Which response headers are available to scripts running in the
  # browser in response to a cross-origin request.
  expose_headers: []

  # Adds the Access-Control-Max-Age header
  # Can be set to a duration in time units 
  # If not set, the header is not included
  max_age: 2h

In Cosmo

Reference

In Cosmo, it is basically the declarative version of the static configuration in the Hive Gateway. So they are not dynamic. You basically provide allowed origins, methods and so on. You cannot provide specific options to a specific origin. Different than Apollo and Hive GW, it supports env vars to provide these options.

My take

I think Apollo's take is better, CORS should be able to be configured dynamically per origin or static as in Hive Gateway and Apollo Router. But the defaults can be flexible as in Hive Gateway rather than stricter Apollo Router. So by default, it can be just wildcard, headers needed by GraphQL and Hive, methods needed by GraphQL etc. Then users can configure and override those if necessary.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions