New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
walkerlangley opened this Issue Mar 10, 2018 · 23 comments

Comments

Projects
None yet
@walkerlangley
Copy link

walkerlangley commented Mar 10, 2018

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

@michaelj-smith

This comment has been minimized.

Copy link

michaelj-smith commented Mar 12, 2018

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.

@michaelj-smith

This comment has been minimized.

Copy link

michaelj-smith commented Mar 12, 2018

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: ""
  });
};
@walkerlangley

This comment has been minimized.

Copy link

walkerlangley commented Mar 13, 2018

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.

@michaelj-smith

This comment has been minimized.

Copy link

michaelj-smith commented Mar 13, 2018

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.

@walkerlangley

This comment has been minimized.

Copy link

walkerlangley commented Mar 13, 2018

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

@walkerlangley

This comment has been minimized.

Copy link

walkerlangley commented Mar 13, 2018

@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

@walkerlangley

This comment has been minimized.

Copy link

walkerlangley commented Mar 13, 2018

Ok. I got it sorted out.

Thanks again for the help!

@snavarro89

This comment has been minimized.

Copy link

snavarro89 commented Mar 22, 2018

walkerlangley, how did you got it sorted out?

@walkerlangley

This comment has been minimized.

Copy link

walkerlangley commented Apr 16, 2018

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

@jfuss jfuss changed the title Any suggestions for CORS besides use serverless.com [Feature Request] CORS Support in start-api May 12, 2018

@jfuss

This comment has been minimized.

Copy link
Contributor

jfuss commented May 12, 2018

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

@rabowskyb

This comment has been minimized.

Copy link

rabowskyb commented May 28, 2018

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.

@ChadElias

This comment has been minimized.

Copy link

ChadElias commented Jun 26, 2018

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

@rabowskyb

This comment has been minimized.

@skylerrichter

This comment has been minimized.

Copy link

skylerrichter commented Jul 24, 2018

@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: "'*'"
@rabowskyb

This comment has been minimized.

Copy link

rabowskyb commented Jul 24, 2018

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

@alechewitt

This comment has been minimized.

Copy link

alechewitt commented Jul 25, 2018

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?

@rabowskyb

This comment has been minimized.

Copy link

rabowskyb commented Jul 26, 2018

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.

@holtc

This comment has been minimized.

Copy link

holtc commented Sep 18, 2018

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
}
@heitorlessa

This comment has been minimized.

Copy link
Contributor

heitorlessa commented Sep 18, 2018

@sgronblo

This comment has been minimized.

Copy link

sgronblo commented Oct 19, 2018

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.

@kkrull

This comment has been minimized.

Copy link

kkrull commented Oct 24, 2018

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.

@tkaria

This comment has been minimized.

Copy link

tkaria commented Nov 25, 2018

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.

@lyxhaven

This comment has been minimized.

Copy link

lyxhaven commented Dec 20, 2018

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 ?

@SoodDhruv SoodDhruv added this to the Backlog milestone Jan 18, 2019

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