Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to allow CORS for custom headers in Serverless?

The core question here is: "how do I allow custom headers in a CORS GET request that is handled with the Serverless framework?". If you know the answer to that, pass Go, collect $200 and please answer that question. If it's not a question with a straight answer, here are the details:

I am writing an app using the Serverless framework on AWS Lambda (the API is managed through AWS API Gateway. Frankly, I'm not entirely sure what that means or what benefit that provides me but that's what Serverless automatically configured for me). I am attempting to create an open API which requires CORS to be enabled. I am using the Lambda Proxy integration. I have followed the practices found here. They have brought me partial success. My app currently has CORS enabled if I do not include my custom headers. However, it still does not work with custom headers.

When I send the following request to my API:

var data = null;

var xhr = new XMLHttpRequest();
xhr.withCredentials = false;

xhr.addEventListener("readystatechange", function () {
  if (this.readyState === 4) {
    console.log(this.responseText);
  }
});

xhr.open("GET", "https://api.spongebobify.com/");
xhr.setRequestHeader("text", "hey");


xhr.send(data);

... I get this error:

Failed to load https://api.spongebobify.com/: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://forum.serverless.com' is therefore not allowed access.

This error message is confirmed if I check the "response headers" using Chrome dev tools: there is no Access-Control-Allow-Origin in the response headers.

However, if I send the same request with the setRequestHeader() commented out, it works perfectly (yes, I know it returns a 403 error: that is intentional behavior).

Here's what I think is happening. My service has two potential CORS problems: domain related (a request not coming from the origin domain) and custom header related (a header not safe-listed by the CORS spec, more here). Somehow, the Serverless framework trips up on the second issue which causes it not even get to the point where it issues the appropriate headers to allow all ("*") domains.

Here is my serverless.yml config file:

# serverless.yml

service: spongebobify

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  region: us-east-1

functions:
  app:
    handler: handler.endpoint
    events:
      - http: GET /
        cors:
            origin: '*'
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
              - Startlower
              - Text
              - Access-Control-Allow-Headers
              - Access-Control-Allow-Origin
            allowCredentials: false

and here is the function that I am trying to run. You can see my many attempts to set the headers properly. I'm 60% convinced that a fix will come via the serverless.yml file at this point.

"use strict";

const spongebobify = require("spongebobify");

module.exports.endpoint = (event, context, callback) => {
  let startLower = event.headers.startlower === "false" ? false : true;
  try {
    const response = {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*", // Required for CORS support to work
        "Access-Control-Allow-Headers": "content-type,origin,text,startlower",
        "Access-Control-Allow-Methods": "GET, OPTIONS",
        "content-type": "text/plain",
        "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS
      },
      body: spongebobify(event.headers.text, startLower)
    };
    callback(null, response);
  } catch (err) {
    console.log(err);
    const response = {
      statusCode: 403,
      headers: {
        "Access-Control-Allow-Origin": "*", // Required for CORS support to work
        "Access-Control-Allow-Headers": "content-type,origin,X-text,startlower",
        "Access-Control-Allow-Methods": "GET, OPTIONS",
        "content-type": "text/plain",
        "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS
      },
      body: "Malformed request."
    };
    callback(null, response);
  }
};

You can replicate my problem my running the above XMLHttpRequest in the dev console on the following sites:

  1. api.spongebobify.com with the custom header enabled or disabled. It will work perfectly in both cases (because it won't be cross origin).
  2. Any site that doesn't have a properly configured CSP with the custom header enabled. The OPTIONS request will fail and it will accurately report that there is no Access-Control-Allow-Origin header
  3. Any site that doesn't have a properly configured CSP without the custom header enabled. The OPTIONS request will pass (which you'll know because Chrome will never tell you that it happened) and you will see the Access-Control-Allow-Origin in the response header. You will also see the response "Malformed request.".
like image 738
Ben Cooper Avatar asked Dec 28 '17 06:12

Ben Cooper


People also ask

How do I enable CORS in AWS?

Enable CORS support on a REST API resourceSign in to the API Gateway console at https://console.aws.amazon.com/apigateway . Choose the API from the APIs list. Choose a resource under Resources. This will enable CORS for all the methods on the resource.

How do I enable CORS in Sam template?

To enable CORS for the Lambda proxy integration, you must add Access-Control-Allow-Origin:domain-name to the output headers. domain-name can be * for any domain name. Save this answer.


1 Answers

I think the issue is that you're mixing the short form of the HTTP event (- http: GET /) with the long form that adds additional options.

Try using this:

functions:
  app:
    handler: handler.endpoint
    events:
      - http: 
          method: GET 
          path: /
          cors:
            origin: '*'
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
              - Startlower
              - Text
              - Access-Control-Allow-Headers
              - Access-Control-Allow-Origin
            allowCredentials: false

The main changes are:

1) Adding method and path keys on the http event object, and

2) Indenting the cors object another level. It was previously at the top level of the http event.

Let me know if this helps :)

like image 147
Alex Avatar answered Oct 20 '22 03:10

Alex