Skip to content

[Feature Request] CORS Support in start-api #323

Closed

Description

@walkerlangley

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

Activity

  1. michaelj-smith commented on Mar 12, 2018

    @michaelj-smith

    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.

  2. michaelj-smith commented on Mar 12, 2018

    @michaelj-smith

    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: ""
      });
    };
    
  3. walkerlangley commented on Mar 13, 2018

    @walkerlangley
    Author

    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.

  4. michaelj-smith commented on Mar 13, 2018

    @michaelj-smith

    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.

  5. walkerlangley commented on Mar 13, 2018

    @walkerlangley
    Author

    @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.

  6. walkerlangley commented on Mar 13, 2018

    @walkerlangley
    Author

    @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

  7. walkerlangley commented on Mar 13, 2018

    @walkerlangley
    Author

    Ok. I got it sorted out.

    Thanks again for the help!

  8. snavarro89 commented on Mar 22, 2018

    @snavarro89

    walkerlangley, how did you got it sorted out?

  9. walkerlangley commented on Apr 16, 2018

    @walkerlangley
    Author

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

  10. changed the title [-]Any suggestions for CORS besides use serverless.com[/-] [+][Feature Request] CORS Support in start-api[/+] on May 12, 2018
  11. jfuss commented on May 12, 2018

    @jfuss
    Contributor

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

  12. rabowskyb commented on May 28, 2018

    @rabowskyb
  13. 24 remaining items

  14. jfuss commented on Sep 16, 2019

    @jfuss
    Contributor

    Release in v0.21.0.

    Closing

  15. michaelj-smith commented on Oct 31, 2019

    @michaelj-smith

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

  16. klanthier commented on Nov 6, 2019

    @klanthier

    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)

  17. franbueno commented on Dec 20, 2019

    @franbueno

    Same issue on version 0.38.0 😓

  18. cesarho commented on Jan 5, 2020

    @cesarho

    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 -

  19. walkerlangley commented on Jan 13, 2020

    @walkerlangley
    Author

    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

        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}'
    
  20. imoby commented on Feb 14, 2020

    @imoby

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

  21. jakebrinkmann commented on Mar 11, 2020

    @jakebrinkmann

    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: "*"
    
  22. dmr07 commented on Nov 8, 2021

    @dmr07

    Solution from @klanthier appears to no longer work with sam version 1.34.1. @jfuss could this issue be re-opened?

  23. towfiqi commented on Nov 21, 2021

    @towfiqi

    Not working anymore...

  24. i-am-mani commented on Dec 25, 2021

    @i-am-mani

    Unable to get sam-cli's start-api working locally without CORS. This feature is pretty critical for testing integrations locally.

  25. emmanuelnk commented on Jun 3, 2022

    @emmanuelnk

    Only the npm install local-cors-proxy solution worked for me. Every other solution did NOT work unfortunately

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions