Description
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.