20

I have a AWS Lambda instance that connects to a defined AWS API Gateway. If I enable CORS and give the access-control-allow-origin a definition of http://example.com, then I am able to access the Lambda instance from http://example.com. However, if I use https://example.com, it doesn't work.

So in AWS, how can I define using multiple access-control-allow-origin values without using a wildcard? I tried using something like *.example.com, but that doesn't work.

EDIT: If I use '*' as my value on the API gateway, but setup CORS rules on my S3 bucket, would that be secure? Example for bucket rules:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>http://example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
    <CORSRule>
        <AllowedOrigin>https://example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
    <CORSRule>
        <AllowedOrigin>https://www.example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Wes
  • 724
  • 1
  • 11
  • 29
  • Same issue here. My situation is that I have to use the withCredentials() option so no wildcard is allowed. I might need to handle the cors headers myself instead of letting apigw handles it. It is so weird that they provide CORS rules for s3 but not for apigatway. – Taichi Sep 22 '16 at 21:21

5 Answers5

47

This has always been an annoyance with CORS if you want to enable several Origins.

The common workaround in other systems (e.g. express/nginx etc) is to:

  • inspect the Origin header sent by the browser
  • check it against a whitelist of origins
  • if it matches, return the incoming Origin as the Access-Control-Allow-Origin header, else return a placeholder (default origin)

This isn't possible using AWS-Gateway's autowired CORS support as uses a mock integration, it is however possible if you write your own code to process the OPTIONS request.

Below is example code written with lambda proxy integrations:

const allowedOrigins = [
    "http://example.com",
    "http://example.com:8080",
    "https://example.com",
    "https?://[a-z]*.?myapp.com",
    "http://localhost:[0-9]*"
];

exports.handler = (event, context) => {
    const origin = event.headers.Origin || event.headers.origin;
    var goodOrigin = false;

    if (origin) {
        allowedOrigins.forEach( allowedOrigin => {
            if (!goodOrigin && origin.match(allowedOrigin)) {
                goodOrigin = true;
            }
        });
    }

    context.succeed({
        headers: {
            "Access-Control-Allow-Headers": "Accept,Accept-Language,Content-Language,Content-Type,Authorization,x-correlation-id",
            "Access-Control-Expose-Headers": "x-my-header-out",
            "Access-Control-Allow-Methods": "DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT",
            "Access-Control-Allow-Origin": goodOrigin ? origin : allowedOrigins[0]
        },
        statusCode: 204
    });
};

Save this as a lambda function. To set this up in API-Gateway add an OPTIONS method and for the Integration Request choose Lambda Function with Use Lambda Proxy integration ticked.

Of course the downside to this is that you are paying for the lambda functions, and calling the lambda function will probably be an extra 50ms latency over the mock integration.

Ravenscar
  • 2,730
  • 19
  • 18
  • I am using cookied based authentication, so how can we configure these core headers for a GET request without using lambda proxy integration ? – Rajan Sharma Jun 22 '20 at 11:53
  • How will the lambda proxy integration work for different API endpoints ? do we need to create separate OPTIONS method for all ? Why not just handle this at the backend API in that case ??? – codeaprendiz Apr 18 '22 at 15:51
24

Why not use Velocity Template language mapping template to check from a list of allowed domains and set the origin header

$input.json("$")
#set($domains = ["https://www.example.com", "https://www.abcd.com"])
#set($origin = $input.params("origin"))
#if($domains.contains($origin))
#set($context.responseOverride.header.Access-Control-Allow-Origin="$origin")
#end
Dhiraj Agarwal
  • 309
  • 3
  • 4
  • Where should this be placed and then what should be there in the default core configuration provided by was? – Rajan Sharma Jun 22 '20 at 11:51
  • 1
    so old thread but still of use, maybe even more with latest HTST issues where it's not so easy anymore to just use your local hosts file to "simulate" live or test domain. Also, this answer deserves much more votes than Lambda ones. Lambda on this is wastefull, you have to pay fro every OPTIONS request completely unnecessary. I just ran into a problem wanting my API test stage to be accessible from local dev server but also throgh Netlify's test server. Used something similar, put it on Response Mapping Templates in every OPTIONS method, similar to every other method that uses CORS. – Mirko Vukušić Jun 29 '20 at 16:20
  • I used something similar for dev: I used this apporach: ```#if( $input.params('origin') == 'https://localhost:8080' && $context.stage == 'test') #set($context.responseOverride.header.Access-Control-Allow-Origin = #end ``` This way default is overridden. – Mirko Vukušić Jun 29 '20 at 16:21
  • 6
    @RajanSharma in the AWS Api Gateway console, the options and post methods' Response Integration -> Content-Type: application/json -> Mapping Template – Willie Z Jul 16 '20 at 08:15
  • Thanks for this one! Worked perfectly! – Chris Phillips Mar 26 '21 at 00:42
  • This solution is the simplest, thanks. One thing I was wondering - where can I find a good reference of the Velocity Template Language? I never would have known array.contains() was an available function from this reference, which is really scant: http://velocity.apache.org/engine/devel/vtl-reference.html – MattS Apr 14 '21 at 16:54
  • Can someone help to explain what's the usage of the first line: $input.json("$")? Thanks! – user6318446 Jul 21 '21 at 01:28
  • This is by far the best answer here, ty – Robert Christ Aug 17 '21 at 16:54
7

Unfortunately this is not possible today. The CORS spec does not allow for partial wild cards and currently API Gateway only allows a single static value for the header.

You may be able to overload your OPTIONS method to return this value dynamically based on the incoming host header.

Bob Kinney
  • 8,870
  • 1
  • 27
  • 35
  • Thanks Bob. I know it is not recommended to use `*` for the value (which works since anything goes), but for a low-profile site do you believe it would cause any issue? – Wes Sep 22 '16 at 02:06
  • 1
    @Wes that depends on the level of risk you are willing to accept. If it's just a matter of http vs https, you can probably solve this by redirecting clients to https always. With [Firefox](https://blog.mozilla.org/security/2015/04/30/deprecating-non-secure-http/) and [Chrome](https://blog.chromium.org/2016/09/moving-towards-more-secure-web.html) moving toward deprecating http, this makes sense and shouldn't be too difficult. – Bob Kinney Sep 22 '16 at 15:44
3

I did something like this:

const handler: APIGatewayProxyHandler = async (event) => {
  const origin = event?.headers?.Origin || event?.headers?.origin;
  const allowedOrigins = ['https://example.com'];
  const headers = {
    'Access-Control-Allow-Origin': allowedOrigins.includes(origin)
      ? origin
      : allowedOrigins[0],
  };

  return {
    headers,
    body: JSON.stringify({
      myResponse: 'data',
    }),
    statusCode: 200,
  };
};

Can then test via chrome dev tools by going to your client domain and running a fetch in the console:

fetch('https://exampleLambda.com/v1/example', { 
   method: 'get',
   mode: 'cors'
   headers: new Headers({
     'Authorization': 'Bearer 12345, 
   }), 
 })
 .then(result => result.json())
 .then(console.log)
Bhetzie
  • 2,852
  • 10
  • 32
  • 43
1

I used API Gateway with Mock Integration to check if the origin header has a https and also a origin domain I trust from a list of trusted domains

  1. In API Gateway Resources Click on any OPTIONS Method and Go to Integration Request
  2. Select radio button When there are no templates defined(recommended)
  3. Click on application/json in Content Type
{"statusCode": 200}
#set($domainsList = ['test.com','abc.in'])
#foreach( $domain in domainList)
    if(($input.params('origin').startsWith("https") && $input.params('origin').endsWith($domain) || $input.params('origin').endsWith("localhost") || $input.params('origin').endsWith("localhost:8100"))
    #set($context.responseOverride.header.Access-Control-Allow-Origin = $input.params('origin'))
    #break
    #end
#end

In the If condition I have also specified localhost since I have to test my apps from localhost:8100 and for Android Apps the Origin header is https://localhost

We can use java based comparators in Apache Velocity Template

-> References : https://velocity.apache.org/engine/1.7/user-guide.html

  • After correcting several syntax errors in the example code, this worked for me. Thanks! – Lee Smith Oct 19 '22 at 17:31
  • 1
    Do not use this! This is a good example of a CORS misconfiguration as it allows an expanding origin exploit as described here: https://crashtest-security.com/cors-misconfiguration/ If you send the origin attacker-test.com it will set this domain as the ACAO. Instead use `$domain` as the value to set. – Ska Dec 08 '22 at 12:18
  • @LeeSmith can you share your code with the bug fixes? – mojave Dec 09 '22 at 17:20