Skip to content

AWS::Serverless::Api does not correctly support multiple CORS Origins #3769

Open
@garretwilson

Description

@garretwilson

The AWS::Serverless::Api CorsConfiguration has an AllowOrigin property that purports to allow multiple origins to be configured for the CORS Access-Control-Allow-Origin header for preflight requests using OPTION:

AllowOrigin
String of origin to allow. This can be a comma-separated list in string format.

However the actual HTTP Access-Control-Allow-Origin header does not permit multiple comma-separated domains as a value. See the MDN docs for Access-Control-Allow-Origin. See also the actual, living spec. (One answer on Stack Overflow indicated that an earlier version of the spec might have allowed a list many years ago, but that is not currently the case and moreover browsers don't support it.)

It is possible for a server to allow multiple origins by dynamically looking at the requested Origin in a preflight request and echoing back the requested Origin if it matches a known list, as discussed in the answers to Access-Control-Allow-Origin Multiple Origin Domains?. And in fact according to the AWS Compute Blog article Configuring CORS on Amazon API Gateway APIs, the SAM support for HTTP APIs does exactly that (while allowing an actual YAML list in the template as well):

Note that the AllowOrigin section allows more than one domain. When the browser makes a request, HTTP APIs checks the list for the incoming origin. If it exists, HTTP APIs adds it to the access-control-allow-origin header in the response.

However for REST APIs, SAM simply copies the comma-separate list. See for example this JSON transformed from a SAM template using AllowOrigin: "'http://localhost,https://example.com'":

                "x-amazon-apigateway-integration": {
                  "type": "mock",
                  "requestTemplates": {
                    "application/json": "{\n  \"statusCode\" : 200\n}\n"
                  },
                  "responses": {
                    "default": {
                      "statusCode": "200",
                      "responseTemplates": {
                        "application/json": "{}\n"
                      },
                      "responseParameters": {
                        "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
                        "method.response.header.Access-Control-Allow-Origin": "'http://localhost,https://example.com'",
                        "method.response.header.Access-Control-Allow-Methods": "'POST, GET'",
                        "method.response.header.Access-Control-Allow-Credentials": "'true'"
                      }
                    }
                  }
                },
                "summary": "CORS support",

Because http://localhost,https://example.com is not an allowed value for Access-Control-Allow-Origin, the browser won't match it; more accurately, it likely considers the entire string as a single domain, so it won't match a request from http://localhost for example.

What SAM should do is parse out the comma-separated list and provide some OPTIONS implementation that checks the request and echoes back the requested Origin if it matches an origin in the list from the template (as the SAM HTTP API purportedly does, which I haven't tested). This would be the best approach. Note that if this is done, a Vary: Origin will need to be added to the responses.

An alternative is to prevent multiple origins altogether for REST APIs (updating the documentation appropriately to remove the description of a comma-separated list of values).

Leaving things as they are is incorrect and simply doesn't work: browsers won't properly match a list of origins in the Access-Control-Allow-Origin header value.

Metadata

Metadata

Assignees

No one assigned

    Labels

    stage/needs-triageAutomatically applied to new issues and PRs, indicating they haven't been looked at.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions