Aws-sam-cli: [Feature Request] CORS Support in start-api

Created on 10 Mar 2018  Â·  41Comments  Â·  Source: aws/aws-sam-cli

Full disclosure, this is my first attempt at working with a SAM template so it's likely I'm doing something wrong, but I can't get CORS working for the life of me. My functions work when I hit the endpoints using Insomnia, but when trying to hit it from my app, I get a 404 on the options request. Also, when I run sam local start-api, I get a message saying WARNING: Could not find function for [OPTIONS] to /login resource right above a message saying Mounting index.login (nodejs6.10) at http://127.0.0.1:3000/login [POST]
Below is my template.yaml. I attempted to follow the example here, but still no luck.

However, if I remove the "options" part of the swagger definition, I don't get the OPTIONS warning.

            options:
              consumes:
              - application/json
              produces:
              - application/json
              responses:
                '200':
                  description: 200 response
                  headers:
                    Access-Control-Allow-Origin:
                      type: string
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Headers:
                      type: string
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: 200
                    responseParameters:
                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                passthroughBehavior: when_no_match
                requestTemplates:
                  application/json: "{\"statusCode\": 200}"
                type: mock

Any help / suggestions would be greatly appreciated

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'

Parameters:
  TableName:
    Default: <TableName>
    Type: String
  FunctionNamePrefix:
    Default: <TableName>
    Type: String
  Secret:
    Default: ""
    Type: String
  AppConfigReadCapacity:
    Default: 5
    Type: Number
  AppConfigWriteCapacity:
    Default: 5
    Type: Number
  AppConfigPrimaryKey:
    Default: id
    Type: String
  Env:
    Default: dev
    Type: String
  S3Bucket:
    Default: <S3Bucket>
    Type: String

Resources:
  EnterpriseApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: dev
      DefinitionBody:
        swagger: '2.0'
        info:
          title: 'API Gateway Endpoints'
        basePath: '/dev'
        schemes:
          - 'https'
        paths:
          "/login":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: "arn:aws:apigateway:${awsRegion}lambda:path/2015-03-31/functions/arn:aws:lambda:${awsRegion}l:${awsAccount}:function:${function}/invocations"
                responses: {}
                passthroughBehavior: when_no_match
            options:
              consumes:
              - application/json
              produces:
              - application/json
              responses:
                '200':
                  description: 200 response
                  headers:
                    Access-Control-Allow-Origin:
                      type: string
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Headers:
                      type: string
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: 200
                    responseParameters:
                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                passthroughBehavior: when_no_match
                requestTemplates:
                  application/json: "{\"statusCode\": 200}"
                type: mock

  LoginFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.login
      Runtime: nodejs6.10
      CodeUri: 's3://${s3Bucket}/${s3Folder}/test.zip'
      FunctionName:
        Fn::Join:
          - "-"
          -
            - Ref: FunctionNamePrefix
            - login
      Description: ''
      Timeout: 30
      Environment:
        Variables:
          TABLE_NAME:
            Ref: TableName
          NODE_ENV:
            Ref: Env
          S3_BUCKET: <BucketName>
          SECRET:
            Ref: Secret
          AWS_PROFILE:
            Ref: AwsProfile
          POSTGRES_HOST:
            Ref: PostgresHost
          POSTGRES_DB:
            Ref: PostgresDb
          POSTGRES_USER:
            Ref: PostgresUser
          POSTGRES_PASSWORD:
            Ref: PostgresPassword
          POSTGRES_DB:
            Ref: PostgresDb
          API_Key:
            Ref: ApiKey
      Events:
        LoginResource:
          Type: Api
          Properties:
            Path: /login
            Method: post
            RestApiId:
              Ref: EnterpriseApi

arelocastart-api priorit2-important stagwaiting-for-release typfeature

Most helpful comment

It occurred to me that a simpler solution than the lambda posted above would probably be to simply proxy all requests to sam.

I did a (very) quick Google and found this: https://www.npmjs.com/package/local-cors-proxy

To make this work, I simply need to start SAM locally, then do this:

npm install local-cors-proxy
npx lcp --proxyUrl http://localhost:3000/

(where "3000" is the port SAM is running)

I then reference the API using http://localhost:8010/proxy/.

Not as good as a fully integrated SAM solution, but at least it doesn't involve any code changes.

All 41 comments

Your options block looks pretty good. I've got a just a few differences, most notably that you don't have a "responseTemplates" property inside the default response, and you don't have the httpMethod property.

              x-amazon-apigateway-integration:
                requestTemplates:
                  application/json: '{"statusCode" : 200}'
                responses:
                  default:
                    statusCode: '200'
                    responseParameters:
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
                      method.response.header.Access-Control-Allow-Methods: "'POST, GET, PUT, DELETE'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                    responseTemplates:
                      application/json: "{}"
                passthroughBehavior: when_no_match
                httpMethod: OPTIONS
                type: mock

The above config is just to get the options endpoint executing in a deployed SAM app. Local execution is a different beast. SAM Local doesn't yet create endpoints for "mock" integration requests. So I've gotten around this by creating an extra Lambda function for local CORS requests:

  # This Lambda Function is defined ONLY for local development,
  # to stub the "AWS_Mock" API Endpoints that will be created when the API Gateway is deployed
  # When deployed to AWS, the API Gateway configuration will override the lambda events defined here
  # We tried using a CloudFormation "Condition" statement on this resource, but that didn't work with SAM Local tools
  resLambdaLocalCorsStub:
    Type: AWS::Serverless::Function
    Properties:
      Handler: corsOptions.handler
      Runtime: nodejs6.10
      FunctionName: !Sub "${paramEnvironment}${paramFeatureBranch}_${paramServiceName}_corsOptions"
      CodeUri: dist
      Timeout: 30
      Events:
        healthOptions: # This block must be repeated for each endpoint that needs CORS support in SAM Local
          Type: Api
          Properties:
            RestApiId: !Ref resApiGateway
            Path: /health
            Method: OPTIONS
        loginOptions:
          Type: Api
          Properties:
            RestApiId: !Ref resApiGateway
            Path: /login
            Method: OPTIONS

Also, once you get the OPTIONS endpoint working, your Lambda function for each endpoint needs to return the "Access-Control-Allow-Origin" header on every response. I've made that header a standard part of the response object from every lambda function.

And here's the content of corsOptions.js:

"use strict";

// ***** This handler is used only in local development, for mocking the OPTIONS responses
// ***** This enables API Tests to pass CORS tests when running locally
exports.handler = (event, context, callback) => {
  callback(null, {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key",
      "Access-Control-Allow-Methods": "POST, GET, PUT, DELETE",
      "Access-Control-Allow-Origin": "*"
    },
    body: ""
  });
};

Thanks for the detailed response @michaelj-smith. I'll take a look at this tomorrow. Seems like a lot of work to allow testing locally :)

This weekend I ended up moving everything over to serverless and got things working pretty easily (that cors: true is pretty nice) but I'm interested to see if I can get things working with sam-local as well.

Your welcome. Sorry to hear you couldn't get it working yet.

I agree that there are some less-than-ideal workarounds needed for SAM Local, but it's still a very new tool. I'm impressed at the active development and general responsiveness to such a wide variety of use cases used by the tool. I recently made the switch to SAM from serverless.com framework, because of the power of all of the CloudFormation tools. Still, SAM might not yet be a full solution for everyone.

There was a big release to the SAM tools today (v1.4.0): https://github.com/awslabs/serverless-application-model/releases/tag/1.4.0
This will undoubtedly help unblock the ability for SAM Local developers to start supporting CORS in a future release of SAM Local.

@michaelj-smith no worries. I'm sure the tool will evolve. Thanks for the link to the new SAM tools and thanks again for all the help.

@michaelj-smith Hate to bother you again, but I feel like I'm so close....

I got the options to return a 200 (huge thanks), however the follow-up POST isn't being sent. Here is my updated template.yml (and I added the corsOptions.js file you added above.) Any ideas what's causing the request to hang?

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'

Parameters:
  TableName:
    Default: <name>
    Type: String
  FunctionNamePrefix:
    Default: <name>
    Type: String
  Secret:
    Default: ""
    Type: String
  AppConfigReadCapacity:
    Default: 5
    Type: Number
  AppConfigWriteCapacity:
    Default: 5
    Type: Number
  AppConfigPrimaryKey:
    Default: id
    Type: String
  Env:
    Default: dev
    Type: String
  S3Bucket:
    Default: <name>
    Type: String

Resources:
  resLambdaLocalCorsStub:
    Type: AWS::Serverless::Function
    Properties:
      Handler: corsOptions.handler
      Runtime: nodejs6.10
      FunctionName: !Sub "${paramEnvironment}${paramFeatureBranch}_${paramServiceName}_corsOptions"
      CodeUri: dist
      Timeout: 30
      Events:
        loginOptions: # This block must be repeated for each endpoint that needs CORS support in SAM Local
          Type: Api
          Properties:
            RestApiId: !Ref EnterpriseApi
            Path: /api/login
            Method: OPTIONS

  Login:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: auth.login
      Runtime: nodejs6.10
      CodeUri: 's3://<name>-<region>/<directory>/latest.zip'
      FunctionName:
        Fn::Join:
          - "-"
          -
            - Ref: FunctionNamePrefix
            - login
      Timeout: 30
      Environment:
        Variables:
          TABLE_NAME: !Ref TableName
          NODE_ENV: !Ref Env
          S3_BUCKET: !Ref S3Bucket
          SECRET: !Ref Secret
          AWS_PROFILE: !Ref AwsProfile
          POSTGRES_HOST: !Ref PostgresHost
          POSTGRES_DB: !Ref PostgresDb
          POSTGRES_USER: !Ref PostgresUser
          POSTGRES_PASSWORD: !Ref PostgresPassword
          POSTGRES_DB: !Ref PostgresDb
          API_Key: !Ref ApiKey
      Events:
        LoginResource:
          Type: Api
          Properties:
            RestApiId: !Ref EnterpriseApi
            Path: /api/login
            Method: post

  EnterpriseApi:
    Type: 'AWS::Serverless::Api'
    Properties:
      StageName: dev
      Cors: "'*'"
      DefinitionBody:
        swagger: '2.0'
        info:
          version: '1.0'
        schemes:
          - 'https'
        produces:
          - 'application/json'
        paths:
          /api/login:
            options:
              x-amazon-apigateway-integration:
                type: mock
                requestTemplates:
                  application/json: '{ "statusCode" : 200 }'
                httpMethod: OPTIONS
                responses:
                  default:
                    statusCode: 200
                    responseParameters:
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                    responseTemplates:
                      application/json: '{}'
              responses:
                '200':
                  headers:
                    Access-Control-Allow-Headers:
                      type: string
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Origin:
                      type: string
            post:
              summary: 'Log users in'
              description: 'For Login Page'
              parameters:
                - name: 'user'
                  in: 'body'
                  description: 'The person to log in.'
                  schema:
                    required:
                      - 'email'
                      - 'password'
                    properties:
                      email:
                        type: 'string'
                      password:
                        type: 'string'
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  'Fn::Sub': >-
                    arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Login.Arn}/invocations
                responses:
                  default:
                    statusCode: 200
                    responseParameters:
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
                      method.response.header.Access-Control-Allow-Methods: "'post'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                    responseTemplates:
                      application/json: '{}'
              responses:
                '200':
                  headers:
                    Access-Control-Allow-Headers:
                      type: string
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Origin:
                      type: string
                400:
                  description: 'Something went wrong'

screen shot 2018-03-13 at 11 27 56 am

Ok. I got it sorted out.

Thanks again for the help!

walkerlangley, how did you got it sorted out?

@snavarro89 Sorry, just seeing this. To be honest I forgot what I did at the time to fix this. Are you having any issues?

Updated description to '[Feature Request] CORS Support in start-api' and adding labels.

Just in case it might be of some use, here's an example of SAM with CORS enabled: https://github.com/aws-samples/startup-kit-serverless-workload.

There's no SAM Template in that repo that you've linked there.

@rabowskyb does this example work with SAM CLI?

This does not seem to do anything for me when using sam local start-api

Globals:
  Api:
    Cors:
      AllowMethods: "'*'"
      AllowHeaders: "'*'"
      AllowOrigin: "'*'"

I did not test this with SAM local in particular; I tested using SAM for a cloud-based deployment.

I am having the same issue as @skylerrichter.

Globals:
  Api:
    Cors:
      AllowMethods: "'*'"
      AllowHeaders: "'*'"
      AllowOrigin: "'*'"

The above code works when I deploy to api gateway, however when running sam local start-api the options method does not respond with any of the headers specified.

I am not sure if this is a seperate issue?

I assume sam local should follow the sam standard, including CORS support, so sam local should eventually support that same syntax mentioned in the immediately previous comment. Probably don't need to open a separate issue.

Is there any update on this?

In the meantime, I just have all methods returning something along the lines of:

return {
  "statusCode": 200,
  "headers": {
    "Access-Control-Allow-Origin": "*"
  },
  "body": body
}

@jfuss - I can give this a try next Friday afternoon (Sep 28th) if you
like; assign that to me

On Tue, 18 Sep 2018 at 12:18 Chris Holt notifications@github.com wrote:

Is there any update on this?

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/awslabs/aws-sam-cli/issues/323#issuecomment-422354331,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ADL4BARiWWE101tEil_bwIjssA2TQYmbks5ucNaPgaJpZM4SlEzo
.

It would be great to have support for the Api.Cors setting in Sam Local.

The current lack of support forces you to do some hacky workarounds such as handling the CORS OPTIONS request in the Lambda code itself.

If nothing else, can the documentation at least be updated to not suggest that the API Gateway handles anything for CORS?

For example, this series of steps led me to believe that CORS configuration would be supported:

  1. Run sam init --runtime dotnetcore2.0
  2. Open template.yaml and follow the reference in the comments to the template format
  3. Follow that to the API section, then to the CORS section.

I'm not seeing anything saying that this configuration would only work under certain circumstances, leading me to believe that it should work. However, my experiences have been much the same as everyone else's using aws-sam-cli 0.6.0.

I'm using sam 0.7.0 and having same issue as described in this thread - namely trying to enable CORS when running sam local start-api -p 9000. What's the current status of this? Thanks in advance!

My 0.02
Ideally, as suggested above, I'd like to simply have something like in Serverless where I can say Cors: true in the template and have this setting make my local functions behave as if I had set Enable CORS in the API Gateway with a mock handling the OPTIONS request.

I'm having the same problem with aws sam cli local. It won't accept Authorization in my request header. Is there any update on this issue for sam cli local ?

Same issue, doesn't seem to work locally :/

We have CORS working nicely through SAM and the API gateway, but not locally. It would have saved a lot of time if the documentation had made it clear that this didn't work. (And would be even better if it did of course! :))

It occurred to me that a simpler solution than the lambda posted above would probably be to simply proxy all requests to sam.

I did a (very) quick Google and found this: https://www.npmjs.com/package/local-cors-proxy

To make this work, I simply need to start SAM locally, then do this:

npm install local-cors-proxy
npx lcp --proxyUrl http://localhost:3000/

(where "3000" is the port SAM is running)

I then reference the API using http://localhost:8010/proxy/.

Not as good as a fully integrated SAM solution, but at least it doesn't involve any code changes.

The solution proposed by @johnc44 works for me and it seems the less invasive one. Hopefully we will get a "start-api" version on a short term.

Cheers

Many thanks @johnc44 for your proxy solution to getting SAM working locally while we wait for a start-api version from AWS.

Thank you @johnc44! it works, but CORS support in aws-sam-cli is a must

We need CORS locally ... What is going on ?

I will jump on this to confirm this is a pain in the * to get the cors to work on sam start-api. I don't know about others but I am using lambda locally with my web-app and not having this is problematic to develop our software. It would be really great if local support could be handled.

We have CORS working nicely through SAM and the API gateway, but not locally. It would have saved a lot of time if the documentation had made it clear that this didn't work. (And would be even better if it did of course! :))

Exactly. Would have saved me a lot of time if this had been documented up front.

+1 For CORS support. It's pretty hard to test locally without it.

Release in v0.21.0.

Closing

Thank you! I've confirmed that this works great locally in the new versions!

Just wanted to let others know that you can now correctly use start-api with CORS settings like so :

Globals:
  Api:
    Cors:
      AllowMethods: "'*'"
      AllowHeaders: "'*'"
      AllowOrigin: "'*'"

This works for me with the latest version (0.30 as of writing)

Same issue on version 0.38.0 😓

I hit the same error. The server return http 200 without Cors headers. Is there any hint on how to get it passed?

SAM CLI, version 0.39.0

2020-01-05 21:40:19 127.0.0.1 - - [05/Jan/2020 21:40:19] "OPTIONS /orders/193061421222 HTTP/1.1" 200 -

I had this working by setting up a "corsHandler" that just returned a status code of 200, but that doesn't seem to be working anymore and neither does adding a Cors "section" in the AWS Sam Template. Any ideas? Cors section of template is below. I also still have the CORS Options handler set up but that doesn't seem to be called (I'm logging before returning the code but the logs aren't printing).

Update: it looks like the issue is the AllowCredentials property isn't setting the Access-Control-Allow-Credentials header to true and I'm getting an error saying it's value is ''.

The Cors response is returning a 200 with

Access-Control-Allow-Headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key
Access-Control-Allow-Methods: DELETE,GET,OPTIONS,POST,PUT
Access-Control-Allow-Origin: http://localhost:8080

sam--version = 0.40.0

EnterpriseApi: Type: 'AWS::Serverless::Api' Properties: StageName: !Ref Env Cors: AllowCredentials: "'true'" AllowMethods: "'POST, GET, PUT, DELETE, OPTIONS'" AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'" #AllowOrigin: "'*'" AllowOrigin: "'http://localhost:8080'" DefinitionBody: swagger: '2.0' info: version: '1.0' title: !Sub '${CompanyName}' description: !Sub 'Endpoints for ${CompanyName}'

I'm having the same issue here, can we open this up again

The answer by @klanthier worked for me and added these headers:

/// OPTIONS / HTTP/1.1
Access-Control-Allow-Origin: "*"
Access-Control-Allow-Methods: "DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT"
Access-Control-Allow-Headers: "*"
Was this page helpful?
0 / 5 - 0 ratings