Repository: serverless/serverless (JavaScript)
Date: 2019-11-22 13:44:35+00:00
Task type: Bug Fix

Issue:
CORS - allowCredentials: false - does not omit response headers Access-Control-Allow-Credentials
# Bug Report

## Description

### What did you do?

Setting up a resource with CORS and set `allowCredentials: false` e.g.

```yaml
# ...

functions:
  getProduct:
    handler: handler.getProduct
    events:
      - http:
          path: product/{id}
          method: get
          cors:
            origin: '*' # <-- Specify allowed origin
            headers: # <-- Specify allowed headers
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
            allowCredentials: false

# ...
```

### What happened?

The response headers of a CORS preflight contained the `access-control-allow-credentials`-header. 

```
access-control-allow-credentials: false
```

### What should've happened?

According to the specifications, the only valid value for this header is `'true'` and should be omitted when false.

> The only valid value for this header is true (case-sensitive). If you don't need credentials, omit this header entirely (rather than setting its value to false).

source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

> Access-Control-Allow-Credentials = %s"true" ; case-sensitive

source: https://fetch.spec.whatwg.org/#http-new-header-syntax

### What's the content of your `serverless.yml` file?

(excerpts)

```yaml
functions:
  getProduct:
    handler: handler.getProduct
    events:
      - http:
          path: product/{id}
          method: get
          cors:
            origin: '*' # <-- Specify allowed origin
            headers: # <-- Specify allowed headers
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
            allowCredentials: false
```

or:


```yaml
functions:
  getProduct:
    handler: handler.getProduct
    events:
      - http:
          path: product/{id}
          method: get
          cors: true
```

### What's the output you get when you use the `SLS_DEBUG=*` environment variable (e.g. `SLS_DEBUG=* serverless deploy`)

(let me know if this is relevant :-) )

### Similar or dependent issues:

- not found

---

### Further information:

I've preparing this issue as wished in the [contribution guidelines](https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md#when-you-propose-a-new-feature-or-bug-fix)  before submitting a PR. 

If you agree on this bug/improvement the code is ready to be submitted as PR (https://github.com/wildhaber/serverless/tree/fix/cors-omit-access-control-alow-credentials-on-false)

The required change is tiny:

```javascript
// lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
      const preflightHeaders = {
        'Access-Control-Allow-Origin': `'${origin}'`,
        'Access-Control-Allow-Headers': `'${config.headers.join(',')}'`,
        'Access-Control-Allow-Methods': `'${config.methods.join(',')}'`,
      };

      // Only set Access-Control-Allow-Credentials when explicitly allowed (omit if false)
      if(config.allowCredentials === true) {
        preflightHeaders['Access-Control-Allow-Credentials'] = `'${config.allowCredentials}'`;
      }
```
[see diff](https://github.com/serverless/serverless/compare/master...wildhaber:fix/cors-omit-access-control-alow-credentials-on-false#diff-872f8cac897abe18c7e8d00884f15926L33-R39)

(+ [Tests](https://github.com/serverless/serverless/compare/master...wildhaber:fix/cors-omit-access-control-alow-credentials-on-false#diff-047d4bdcd95d2284771376374067d708) / + [Docs](https://github.com/serverless/serverless/compare/master...wildhaber:fix/cors-omit-access-control-alow-credentials-on-false#diff-871929e390d61a5d2a3b505ac9b4bf24R287))


Issue discussion:

Solution patch:
diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md
index aa43826ed8f..1c014b95d99 100644
--- a/docs/providers/aws/events/apigateway.md
+++ b/docs/providers/aws/events/apigateway.md
@@ -284,6 +284,8 @@ Please note that since you can't send multiple values for [Access-Control-Allow-
 
 Configuring the `cors` property sets [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin), [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers), [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods),[Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) headers in the CORS preflight response.
 
+Please note that the [Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials)-Header is omitted when not explicitly set to `true`.
+
 To enable the `Access-Control-Max-Age` preflight response header, set the `maxAge` property in the `cors` object:
 
 ```yml
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
index 7f90c3134a9..b45dee786e2 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
@@ -30,9 +30,13 @@ module.exports = {
         'Access-Control-Allow-Origin': `'${origin}'`,
         'Access-Control-Allow-Headers': `'${config.headers.join(',')}'`,
         'Access-Control-Allow-Methods': `'${config.methods.join(',')}'`,
-        'Access-Control-Allow-Credentials': `'${config.allowCredentials}'`,
       };
 
+      // Only set Access-Control-Allow-Credentials when explicitly allowed (omit if false)
+      if (config.allowCredentials) {
+        preflightHeaders['Access-Control-Allow-Credentials'] = `'${config.allowCredentials}'`;
+      }
+
       // Enable CORS Max Age usage if set
       if (_.has(config, 'maxAge')) {
         if (_.isInteger(config.maxAge) && config.maxAge > 0) {


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js
index 4415a817e84..c81e2f107fc 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js
@@ -157,7 +157,7 @@ describe('#compileCors()', () => {
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
           .ApiGatewayMethodUsersUpdateOptions.Properties.Integration.IntegrationResponses[0]
           .ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
-      ).to.equal("'false'");
+      ).to.be.undefined;
 
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
@@ -194,7 +194,7 @@ describe('#compileCors()', () => {
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
           .ApiGatewayMethodUsersDeleteOptions.Properties.Integration.IntegrationResponses[0]
           .ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
-      ).to.equal("'false'");
+      ).to.be.undefined;
 
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
@@ -233,7 +233,7 @@ describe('#compileCors()', () => {
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
           .ApiGatewayMethodUsersAnyOptions.Properties.Integration.IntegrationResponses[0]
           .ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
-      ).to.equal("'false'");
+      ).to.be.undefined;
     });
   });
 
diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js
index c519823219c..adf302225b6 100644
--- a/tests/integration-all/api-gateway/tests.js
+++ b/tests/integration-all/api-gateway/tests.js
@@ -117,7 +117,7 @@ describe('AWS - API Gateway Integration Test', function() {
         ].join(',');
         expect(headers.get('access-control-allow-headers')).to.equal(allowHeaders);
         expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET');
-        expect(headers.get('access-control-allow-credentials')).to.equal('false');
+        expect(headers.get('access-control-allow-credentials')).to.equal(null);
         // TODO: for some reason this test fails for now...
         // expect(headers.get('access-control-allow-origin')).to.equal('*');
       });