question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Problem with OPTIONS requests

See original GitHub issue

Description:

Trying to access my API from a test app in my browser and the OPTIONS request is being handled by SAM CLI API Gateway but it’s not returning the configuration specified in my template file.

The weird part is that if I try to access a different endpoint from the same template it executes the lambda function (as expected since that’s what SAM Local did).

Additional environment details (Ex: Windows, Mac, Amazon Linux etc) Mac

Output of sam --version: SAM CLI, version 0.3.0

Optional Debug logs:

> export AWS_SDK_LOAD_CONFIG=true && sam local start-api --docker-network myblueprint  "--debug-port" "5858"

2018-05-10 16:23:38 Mounting HealthCheckFunction at http://127.0.0.1:3000/healthcheck [OPTIONS, GET]
2018-05-10 16:23:38 Mounting EnsureUserFunction at http://127.0.0.1:3000/ensure_user [OPTIONS, PUT]
2018-05-10 16:23:38 Mounting GraphQLServerFunction at http://127.0.0.1:3000/graphql [GET, POST, OPTIONS]
2018-05-10 16:23:38 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2018-05-10 16:23:38  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
2018-05-10 16:24:07 127.0.0.1 - - [10/May/2018 16:24:07] "OPTIONS /graphql HTTP/1.1" 200 -
2018-05-10 16:24:45 Invoking dist/healthcheck.handler (nodejs8.10)
2018-05-10 16:24:45 Found credentials in shared credentials file: ~/.aws/credentials

Fetching lambci/lambda:nodejs8.10 Docker container image......
2018-05-10 16:24:49 Mounting /Users/stefan/Projects/blueprint-api/packages/api as /var/task:ro inside runtime container
Debugger listening on ws://0.0.0.0:5858/698de0ac-e3af-4f27-b5dd-bf3caf4cbc58
For help see https://nodejs.org/en/docs/inspector
Debugger attached.
Debugger listening on ws://0.0.0.0:5858/698de0ac-e3af-4f27-b5dd-bf3caf4cbc58
For help see https://nodejs.org/en/docs/inspector
START RequestId: 2ee985d7-416b-182e-ec4a-79af497b0cc2 Version: $LATEST
2018-05-10T04:25:06.756Z        2ee985d7-416b-182e-ec4a-79af497b0cc2    ---TRIMMED ERROR---
END RequestId: 2ee985d7-416b-182e-ec4a-79af497b0cc2
REPORT RequestId: 2ee985d7-416b-182e-ec4a-79af497b0cc2  Duration: 2838.28 ms    Billed Duration: 2900 ms        Memory Size: 1024 MB    Max Memory Used: 52 MB
2018-05-10 16:25:03 No Content-Type given. Defaulting to 'application/json'.
2018-05-10 16:25:03 127.0.0.1 - - [10/May/2018 16:25:03] "OPTIONS /healthcheck HTTP/1.1" 503 -

Template file for reference

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

Parameters:
  Environment:
    Type: String
    AllowedValues:
      - DEVELOPMENT
      - STAGING
      - PRODUCTION

# DEV SHOULD NEVER BE RUN OUTSIDE OF SAM LOCAL
Mappings:
  Config:
    'DEVELOPMENT':
      EnvironmentLowerCase: 'development'
      HostedZoneName: 'localhost'
      HostedZoneId: 'NONE'
      S3FilesBucket: 'blah'
    'STAGING':
      EnvironmentLowerCase: 'staging'
      HostedZoneName: '<REDACTED>'
      HostedZoneId: '<REDACTED>'
      S3FilesBucket: '<REDACTED>'
    'PRODUCTION':
      EnvironmentLowerCase: 'production'
      HostedZoneName: '<REDACTED>'
      HostedZoneId: '<REDACTED>'
      S3FilesBucket: '<REDACTED>'

Conditions:
  CreateS3FileProcessingResources:
    !Equals [!Ref 'AWS::Region', 'ap-southeast-2']

Globals:
  Function:
      Runtime: 'nodejs8.10'
      MemorySize: 1024
      Timeout: 5
      Tracing: PassThrough
      Environment:
        Variables:
          CODE_ENVIRONMENT: !Ref Environment

Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: 'sts:AssumeRole'
          Effect: Allow
          Principal:
            Service: 'lambda.amazonaws.com'
      Path: /
      ManagedPolicyArns:
      - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      - 'arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess'
      Policies:
      - PolicyName: s3
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Action:
            - 's3:*'
            Effect: Allow
            Resource: '*'
      - PolicyName: parameters
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - 'ssm:GetParameter'
            - 'ssm:GetParameters'
            - 'ssm:GetParametersByPath'
            Resource:
            - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${Environment}/API/*'
          - Effect: Allow
            Action:
            - 'kms:Decrypt'
            Resource:
            - !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*'

  MyBlueprintApi:
    Type: 'AWS::Serverless::Api'
    Properties:
      StageName: 'prod'
      EndpointConfiguration: 'REGIONAL'
      Cors:
        AllowMethods: "'GET,OPTIONS,POST,PUT'"
        AllowHeaders: "'Authorization,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'*'"
        MaxAge: "'86400'"
      DefinitionBody:
        swagger: '2.0'
        info:
          version: '0.0.1'
          title:
            'Fn::Sub':
            - 'myblueprint-${EnvironmentLowerCase}'
            - EnvironmentLowerCase: !FindInMap [Config, !Ref Environment, EnvironmentLowerCase]
        basePath: '/prod'
        schemes:
        - 'https'
        paths:
          '/graphql':
            get:
              consumes:
              - 'application/json'
              produces:
              - 'application/json'
              responses:
                '200':
                  description: '200 response'
                  schema:
                    $ref: '#/definitions/Empty'
                  headers:
                    Access-Control-Allow-Origin:
                      type: 'string'
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: '200'
                    responseParameters:
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GraphQLServerFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_templates'
                httpMethod: 'POST'
                contentHandling: 'CONVERT_TO_TEXT'
                type: 'aws_proxy'
            post:
              consumes:
              - 'application/json'
              produces:
              - 'application/json'
              responses:
                '200':
                  description: '200 response'
                  schema:
                    $ref: '#/definitions/Empty'
                  headers:
                    Access-Control-Allow-Origin:
                      type: 'string'
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: '200'
                    responseParameters:
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GraphQLServerFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_templates'
                httpMethod: 'POST'
                contentHandling: 'CONVERT_TO_TEXT'
                type: 'aws_proxy'
            options:
              consumes:
              - 'application/json'
              produces:
              - 'application/json'
              responses:
                '200':
                  description: '200 response'
                  schema:
                    $ref: '#/definitions/Empty'
                  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: "'GET,OPTIONS,POST'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                requestTemplates:
                  application/json: '{"statusCode": 200}'
                uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GraphQLServerFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_match'
                type: 'mock'
          '/ensure_user':
            put:
              consumes:
              - 'application/json'
              produces:
              - 'application/json'
              responses:
                '200':
                  description: '200 response'
                  schema:
                    $ref: '#/definitions/Empty'
                  headers:
                    Access-Control-Allow-Origin:
                      type: 'string'
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: '200'
                    responseParameters:
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EnsureUserFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_templates'
                httpMethod: 'POST'
                contentHandling: 'CONVERT_TO_TEXT'
                type: 'aws_proxy'
            options:
              consumes:
              - 'application/json'
              produces:
              - 'application/json'
              responses:
                '200':
                  description: '200 response'
                  schema:
                    $ref: '#/definitions/Empty'
                  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: "'PUT,OPTIONS'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                requestTemplates:
                  application/json: '{"statusCode": 200}'
                uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EnsureUserFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_match'
                type: 'mock'
          '/healthcheck':
            get:
              consumes:
              - 'application/json'
              produces:
              - 'application/json'
              responses:
                '200':
                  description: '200 response'
                  schema:
                    $ref: '#/definitions/Empty'
                  headers:
                    Access-Control-Allow-Origin:
                      type: 'string'
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: '200'
                    responseParameters:
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HealthCheckFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_templates'
                httpMethod: 'POST'
                contentHandling: 'CONVERT_TO_TEXT'
                type: 'aws_proxy'
            options:
              consumes:
              - 'application/json'
              produces:
              - 'application/json'
              responses:
                '200':
                  description: '200 response'
                  schema:
                    $ref: '#/definitions/Empty'
                  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: "'GET,OPTIONS'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                requestTemplates:
                  application/json: '{"statusCode": 200}'
                uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HealthCheckFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_match'
                type: 'mock'
        definitions:
          Empty:
            type: 'object'
            title: 'Empty Schema'

  GraphQLServerFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: 'dist/graphql.handler'
      CodeUri: './packages/api/'
      Role: !GetAtt LambdaExecutionRole.Arn
      MemorySize: 1536
      Timeout: 30
      Events:
        GetResource:
          Type: Api
          Properties:
            RestApiId: !Ref MyBlueprintApi
            Path: '/graphql'
            Method: get
        OptionsResource:
          Type: Api
          Properties:
            RestApiId: !Ref MyBlueprintApi
            Path: '/graphql'
            Method: options
        PostResource:
          Type: Api
          Properties:
            RestApiId: !Ref MyBlueprintApi
            Path: '/graphql'
            Method: post

  EnsureUserFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: 'dist/ensure_user.handler'
      CodeUri: './packages/api/'
      Role: !GetAtt LambdaExecutionRole.Arn
      Events:
        PutResource:
          Type: Api
          Properties:
            RestApiId: !Ref MyBlueprintApi
            Path: '/ensure_user'
            Method: put
        OptionsResource:
          Type: Api
          Properties:
            RestApiId: !Ref MyBlueprintApi
            Path: '/ensure_user'
            Method: options

  S3FileProcessingFunction:
    Type: 'AWS::Serverless::Function'
    Condition: 'CreateS3FileProcessingResources'
    Properties:
      Handler: 'dist/s3files.handler'
      CodeUri: './packages/api/'
      Role: !GetAtt LambdaExecutionRole.Arn
      Timeout: 30
# Cant use this because the bucket must be created in the same template
#      Events:
#        ObjectCreated:
#          Type: S3
#          Properties:
#            Bucket: !FindInMap [Config, !Ref Environment, S3FilesBucket]
#            Events: s3:ObjectCreated:*

  HealthCheckFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: 'dist/healthcheck.handler'
      CodeUri: './packages/api/'
      Role: !GetAtt LambdaExecutionRole.Arn
      Events:
        GetResource:
          Type: Api
          Properties:
            RestApiId: !Ref MyBlueprintApi
            Path: '/healthcheck'
            Method: get
        OptionsResource:
          Type: Api
          Properties:
            RestApiId: !Ref MyBlueprintApi
            Path: '/healthcheck'
            Method: options
  
  # TODO: update this to use DNS validation when CF supports it
  ApiCertificate:
    Type: 'AWS::CertificateManager::Certificate'
    Properties:
      DomainName:
        'Fn::Sub':
        - 'api.${HostedZoneName}'
        - HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
      DomainValidationOptions:
      - DomainName:
          'Fn::Sub':
          - 'api.${HostedZoneName}'
          - HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
        ValidationDomain: 'myblueprint.cloud'
  
  CustomResourceLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: 'sts:AssumeRole'
          Effect: Allow
          Principal:
            Service: 'lambda.amazonaws.com'
      Path: /
      ManagedPolicyArns:
      - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies:
      - PolicyName: ApiGateway
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Action:
            - 'apigateway:*'
            Effect: Allow
            Resource: '*'
  
  DomainNameInfoCustomResourceFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Handler: index.handler
      Role: !GetAtt CustomResourceLambdaExecutionRole.Arn
      Runtime: 'nodejs6.10'
      Timeout: 300
      Code:
        ZipFile: |
          const AWS = require('aws-sdk');
          const response = require('cfn-response');

          exports.handler = function(event, context) {
              const ApiGateway = new AWS.APIGateway();
              ApiGateway.getDomainName({
                  domainName: event.ResourceProperties.DomainName
              }, (err, data) => {
                  if (err != null) {
                      response.send(event, context, response.FAILED, undefined);
                  } else {
                      response.send(event, context, response.SUCCESS, {
                          DomainName: data.domainName,
                          RegionalDomainName: data.regionalDomainName,
                          RegionalHostedZoneId: data.regionalHostedZoneId,
                          DistributionDomainName: data.distributionDomainName,
                          DistributionHostedZoneId: data.distributionHostedZoneId
                      });
                  }
              });
          }

  ApiDomainName:
    Type: 'AWS::ApiGateway::DomainName'
    Properties:
      DomainName:
        'Fn::Sub':
        - 'api.${HostedZoneName}'
        - HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
      EndpointConfiguration:
        Types:
        - REGIONAL
      RegionalCertificateArn: !Ref ApiCertificate

  ApiBasePathMapping:
    Type: 'AWS::ApiGateway::BasePathMapping'
    DependsOn: [MyBlueprintApi, ApiDomainName]
    Properties:
      DomainName:
        'Fn::Sub':
        - 'api.${HostedZoneName}'
        - HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
      RestApiId: !Ref MyBlueprintApi
      Stage: 'prod'
  
  ApiDomainNameInfo:
    Type: 'Custom::DomainNameInfo'
    DependsOn: [ApiDomainName, ApiBasePathMapping]
    Properties:
      ServiceToken: !GetAtt DomainNameInfoCustomResourceFunction.Arn
      DomainName: !Ref ApiDomainName

  ApiHealthCheck:
    Type: 'AWS::Route53::HealthCheck'
    Properties:
      HealthCheckConfig:
        Port: 443
        Type: 'HTTPS_STR_MATCH'
        SearchString: 'ok'
        ResourcePath: '/prod/healthcheck'
        FullyQualifiedDomainName: !Sub '${MyBlueprintApi}.execute-api.${AWS::Region}.amazonaws.com'
        RequestInterval: 60
        FailureThreshold: 2
      HealthCheckTags:
      - Key: Name
        Value:
          'Fn::Sub':
          - 'api-regional-${EnvironmentLowerCase}-${Region}'
          - EnvironmentLowerCase: !FindInMap [Config, !Ref Environment, EnvironmentLowerCase]
            Region: !Ref 'AWS::Region'

  ApiRecordSet:
    Type: 'AWS::Route53::RecordSet'
    DependsOn: [ApiDomainNameInfo]
    Properties:
      HostedZoneId: !FindInMap [Config, !Ref Environment, HostedZoneId]
      Name:
        'Fn::Sub':
        - 'lbr-api.${HostedZoneName}'
        - HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
      ResourceRecords:
      - !GetAtt ApiDomainNameInfo.RegionalDomainName
      Region: !Ref 'AWS::Region'
      SetIdentifier: !Sub 'api-${AWS::Region}'
      HealthCheckId: !Ref ApiHealthCheck
      Type: CNAME
      TTL: 60

Outputs:
  RestAPIID:
    Description: Rest API ID
    Value: !Ref MyBlueprintApi
  ApiUrl:
    Description: URL of your API endpoint
    Value: !Ref ApiRecordSet
  HealthcheckApiUrl:
    Description: URL of your API health check endpoint
    Value: !Sub 'https://${MyBlueprintApi}.execute-api.${AWS::Region}.amazonaws.com/prod/healthcheck'

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:3
  • Comments:33 (14 by maintainers)

github_iconTop GitHub Comments

7reactions
cl0ckworkcommented, Jan 17, 2019

Hi, I’ve got the same issue. Getting 403 for OPTIONS requests to my graphql endpoint for sam local. Is there any work around for this at the moment? CLI version 0.10.0 @jfuss

@rayhaanq Give this a try in your cloudformation yaml

Globals:
  Api:
    # enable CORS; to make more specific, change the origin wildcard
    # to a particular domain name, e.g. "'www.example.com'"
    Cors:
      AllowMethods: "'OPTIONS,GET,POST,PUT,DELETE'"
      AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
      AllowOrigin: "'*'"

Resources:
   OptionsFunction:
    Type: AWS::Serverless::Function
    Properties:
# create a custom handler to return 200 and appropriate headers for your OPTIONS requests
      FunctionName: options-handler
      Handler: index.options #nodejs
      Events:
        Options:
          Type: Api
          Properties:
            Path: /{cors+}
            Method: OPTIONS
            Auth:
              Authorizer: NONE
4reactions
chau-bao-longcommented, Apr 17, 2019

Finally make it work after a long day suffer from this issue.

It’s a good idea to write another lambda for options method which response successful pre-light check.

I didn’t notice that AWS has mentioned it in their docs [https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html](how to cors)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why is an OPTIONS request sent and can I disable it?
Options request is a preflight request when you send (post) any data to another domain. It's a browser security issue. But we can...
Read more >
OPTIONS - HTTP - MDN Web Docs
The HTTP OPTIONS method requests permitted communication options for a given URL or server. A client can specify a URL with this method, ......
Read more >
Why is my browser sending an OPTIONS HTTP request ...
As far as I know, only requests that are meant to be sent to a different origin and are not a form content-type...
Read more >
Request not responding, Why an OPTIONS method is sent ...
I've been having a problem i don't really have using jQuery Ajax methods, when i use axios, my request sends one Request Method:OPTIONS...
Read more >
Why Is an OPTIONS Request Sent? - Baeldung
The OPTIONS request mentioned in the introduction is a preflight request, which is part of the CORS (Cross-Origin Resource Sharing).
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found