Search code examples
amazon-web-servicescorsaws-api-gatewayaws-cloudformation

AWS API Gateway with Custom Authorizer and CORS Intermittent 200 then 403 then 200 ... Strange


I have an 1 Amazon Api Gateway setup with a custom authorizer (the authorizer basically just returns allow for anything)

I enabled CORS, and this is running from jQuery webpage.

I have two method

  1. /vehicles (returns a list of car)
  2. /bookings (returns booking details)

The behavior I am seeing, is the first request goes fine, I see it pull the OPTIONS, then perform a GET request. Then, I hit the other method the OPTIONS works, then the get returns a 403, but if I launch the request again (On the same resource), I get a 200

I'm using Cloudformation, but I noticed the same behaviour when I was using the Serverless Framework.

Below are some screen shots for my sanity and hopefully someone else has seen this strangeness.

enter image description here

enter image description here

Below is a portion of my Cloudformation YAML template, I'm learning this as I do it.

 HelloAPI:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Sub ${Environment}
      DefinitionBody:
        swagger: 2.0
        info:
          title:
            Ref: AWS::StackName
        securityDefinitions:
          test-authorizer:
            type: apiKey
            name: Authorization
            in: header
            x-amazon-apigateway-authtype: custom
            x-amazon-apigateway-authorizer:
              type: token
              authorizerUri:
                Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthorizerFunc.Arn}/invocations
              authorizerResultTtlInSeconds: 5
        paths:
          /vehicles:
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${VehiclesLambda.Arn}/invocations
              responses: {}
              security:
                - test-authorizer: []
            options:
              tags:
              - "CORS"
              summary: "CORS support"
              description: "Enable CORS by returning correct headers\n"
              consumes:
              - "application/json"
              produces:
              - "application/json"
              parameters: []
              responses:
                "200":
                  description: "Default response for CORS method"
                  headers:
                    Access-Control-Allow-Headers:
                      type: "string"
                    Access-Control-Allow-Methods:
                      type: "string"
                    Access-Control-Allow-Origin:
                      type: "string"
              x-amazon-apigateway-integration:
                type: "mock"
                requestTemplates:
                  application/json: "{\n  \"statusCode\" : 200\n}\n"
                responses:
                  default:
                    statusCode: "200"
                    responseParameters:
                      method.response.header.Access-Control-Allow-Headers: "'X-Amz-Date,Authorization,X-Api-Key'"
                      method.response.header.Access-Control-Allow-Methods: "'*'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                    responseTemplates:
                      application/json: "{}\n"
          /bookings:
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BookingsLambda.Arn}/invocations
              responses: {}
              security:
                - test-authorizer: []
            options:
              tags:
              - "CORS"
              summary: "CORS support"
              description: "Enable CORS by returning correct headers\n"
              consumes:
              - "application/json"
              produces:
              - "application/json"
              parameters: []
              responses:
                "200":
                  description: "Default response for CORS method"
                  headers:
                    Access-Control-Allow-Headers:
                      type: "string"
                    Access-Control-Allow-Methods:
                      type: "string"
                    Access-Control-Allow-Origin:
                      type: "string"
              x-amazon-apigateway-integration:
                type: "mock"
                requestTemplates:
                  application/json: "{\n  \"statusCode\" : 200\n}\n"
                responses:
                  default:
                    statusCode: "200"
                    responseParameters:
                      method.response.header.Access-Control-Allow-Headers: "'X-Amz-Date,Authorization,X-Api-Key'"
                      method.response.header.Access-Control-Allow-Methods: "'*'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                    responseTemplates:
                      application/json: "{}\n"

This is my Anything Goes Authorizer:

'use strict';

const generatePolicy = function(principalId, effect, resource) {
    const authResponse = {};
    authResponse.principalId = principalId;
    if (effect && resource) {
        const policyDocument = {};
        policyDocument.Version = '2012-10-17';
        policyDocument.Statement = [];
        const statementOne = {};
        statementOne.Action = 'execute-api:Invoke';
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement[0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }
    return authResponse;
};

exports.handler = (event, context, callback) => {

    console.log("Hit Authorizer")
    console.log(event)


    callback(null, generatePolicy('user123', 'Allow', event.methodArn));

}; 

Anyone else seen this, or know how to debug it ?

I put this on a test site, just it some one wants to see what I am seeing.

https://s3.amazonaws.com/stackoverflowisgreat2/index.html


Solution

  • In the custom authorizer code, at the line

    statementOne.Resource = resource;
    

    change your resources to this format "arn:aws:execute-api:us-west-2:123456789012:ymy8tbxw7b/*/GET/".

    In your case to allow all that would be:

    statementOne.Resource = arn:aws:execute-api:us-west-2:123456789012:ymy8tbxw7b/*/*/
    

    This is how AWS understands your authorizer. Because in custom authorizer you can get information from the request header like user, group, etc and then validate the info against your authorization database and decide who or what is allowed to continue the request type POST/GET/OPTION, but API gateway won't know your decision until you provide it with a valid answer in AWS format

    {
      "principalId": "yyyyyyyy", // The principal user identification associated with the token sent by the client.
      "policyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": "execute-api:Invoke",
            "Effect": "Allow|Deny",
            "Resource": "arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]"
          }
        ]
      },
      "context": {
        "stringKey": "value",
        "numberKey": "1",
        "booleanKey": "true"
      },
      "usageIdentifierKey": "{api-key}"  # Optional
    }
    

    You can visit this page to understand more about it:

    https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html