Allowing multiple CORS origins with AWS API Gateway

I recently had the need to embed the same form in two different subdomains of the same website. The form is backed by an AWS Lambda function, via API gateway.

I immediately ran into a CORS issue, where the form under the new subdomain couldn’t be submitted because the origin did not match what was returned by the preflight request.

(If you’re unfamiliar with CORS - this post is very useful)

The CORS specification does not allow multiple endpoints to be defined in the Access-Control-Allow-Origin response header. To resolve this, I needed the OPTIONS endpoint in AWS Gateway to dynamically return the origin of the request (checking that this origin is whitelisted).

First, I created a Lambda function that inspects the origin of the request, and returns that origin in the Access-Control-Allow-Origin header if it’s whitelisted.

The Lambda code looks like this (my use case was just for two domains - you could easily refactor to check an array of valid origins, or even call into some data source):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
exports.handler = async (event) => {

let origin = 'https://example.com';

if (event.headers !== null && event.headers !== undefined && event.headers['origin'] !== undefined) {

console.log("Received origin header: " + event.headers.origin);

if(event.headers.origin === 'https://new-subdomain.example.com') {
origin = event.headers.origin;
}
} else {
console.log('No origin header received');
}

const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin" : origin,
"Access-Control-Allow-Headers" : 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
"Access-Control-Allow-Methods": "POST, OPTIONS"
}
};
return response;
};

To hook this up to API Gateway, I changed the Integration Request of the OPTIONS endpoint to use my new Lambda, like so:

Configure Lambda integration

Be sure to have Use Lambda Proxy integration enabled - this will pass through the headers on the event object to your new Lambda.

Remember - your main request (i.e. not the preflight OPTIONS request) will also need to have the Access-Control-Allow-Origin header correctly returned. I found that returning * for this endpoint worked fine, and the browser respected the initial origin returned by the preflight request.