Search code examples
amazon-web-servicesaws-samaws-api-gateway-v2

AWS::ApiGatewayV2::Api - Simple WebSocket configuration throwing INVALID_API_KEY (forbidden)


I did not set any authorization for the Websocket and it throws INVALID_API_KEY.

Here's SAM template:

##############################################################
# API GATEWAY: BrokerAuthenticateSocketApi
##############################################################
  BrokerAuthenticateSocketApi:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: !Sub "${AWS::StackName}-BrokerAuthenticateSocketApi"
      ProtocolType: WEBSOCKET
      RouteSelectionExpression: "$request.body.action"
      Tags:
        'Joba:Product': !Ref Product
        'Joba:Environment': !Ref Environment

  ApiStage: # Why would we need this: https://medium.com/@TomKeeber/aws-api-gateways-c048cec63046
    Type: AWS::ApiGatewayV2::Stage
    Properties:
      StageName: !Ref ApiStageName
      AutoDeploy: true
      ApiId: !Ref BrokerAuthenticateSocketApi
      AccessLogSettings:
        DestinationArn: !GetAtt ApiGatewayAccessLogGroup.Arn
        Format: '{"requestTime":"$context.requestTime","requestId":"$context.requestId","routeKey":"$context.routeKey","status":$context.status,"responseLatency":$context.responseLatency,"integrationRequestId":"$context.integration.requestId","functionResponseStatus":"$context.integration.status","integrationLatency":"$context.integration.latency","integrationServiceStatus":"$context.integration.integrationStatus","ip":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent","principalId":"$context.authorizer.principalId","validationErrorString":"$context.error.validationErrorString","integrationErrorMessage":"$context.integrationErrorMessage","errorMessage":"$context.error.message","errorResponseType":"$context.error.responseType"}'
      Tags:
        'Joba:Product': !Ref Product
        'Joba:Environment': !Ref Environment

  ApiAuthenticateRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref BrokerAuthenticateSocketApi
      RouteKey: authenticate
      AuthorizationType: NONE # TODO: change this for Auth0
      OperationName: AuthenticateRoute
      Target: !Join 
        - /
        - - integrations
          - !Ref ApiAuthenticateRouteIntegration

  ApiAuthenticateRouteIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref BrokerAuthenticateSocketApi
      IntegrationType: AWS
      IntegrationMethod: POST
      IntegrationUri: !Sub "arn:aws:apigateway:${AWS::Region}:states:action/StartExecution"
      CredentialsArn: !Sub "${ApiIntegrationStateMachineExecutionRole.Arn}" 
      TemplateSelectionExpression: \$default
      RequestTemplates: # see: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html
        "$default" : 
          Fn::Sub: >
            #set($statesInput='{"data": ' + $input.body + ',"webSocket": {"connectionId": "' + $context.connectionId + '", "domainName": "' + $context.domainName + '"}' + '}')
            #set($statesInput=$util.escapeJavaScript($statesInput).replaceAll("\\'","'"))
            { 
              "input": "$statesInput",
              "stateMachineArn": "${DownloadBrokerageNotesStateMachine}"
            }

  ApiAuthenticateRouteResponse: # https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-route-response.html
    Type: AWS::ApiGatewayV2::RouteResponse
    Properties:
      RouteId: !Ref ApiAuthenticateRoute
      ApiId: !Ref BrokerAuthenticateSocketApi
      RouteResponseKey: $default

  ApiAuthenticateRouteIntegrationResponse:
    Type: AWS::ApiGatewayV2::IntegrationResponse
    Properties: 
      ApiId: !Ref BrokerAuthenticateSocketApi
      IntegrationId: !Ref ApiAuthenticateRouteIntegration
      IntegrationResponseKey: $default

  ApiIntegrationStateMachineExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - "sts:AssumeRole"
            Effect: Allow
            Principal:
              Service:
                - !Sub apigateway.${AWS::Region}.amazonaws.com
      Path: "/"
      Policies:
        - PolicyName: StateMachineExecutionAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "states:StartExecution"
                Resource: !Ref DownloadBrokerageNotesStateMachine
      Tags:
        - Key: 'Joba:Product'
          Value: !Ref Product
        - Key: 'Joba:Environment'
          Value: !Ref Environment

  ApiGatewayAccessLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Join [ "/", [ "joba", "apigateway", !Ref BrokerAuthenticateSocketApi, "access-logs"]]

Using Postman to test, it connects successfully to the websocket: enter image description here

When I try to send a message (with action=authenticate as configured in the SAM template), it returns Forbidden enter image description here

The apigatway log shows INVALID_API_KEY.

{
"requestTime":"28/Jan/2023:22:32:34 +0000",
"requestId":"feZUdEH2oAMFe9A=",
"routeKey":"-",
"status":403,
"responseLatency":-,
"integrationRequestId":"-",
"functionResponseStatus":"-",
"integrationLatency":"-",
"integrationServiceStatus":"-",
"userAgent":"-","principalId":"-",
"validationErrorString":"-",
"integrationErrorMessage":"-",
"errorMessage":"Forbidden",
"errorResponseType":"INVALID_API_KEY"
}

In no section of the SAM template, I specified Authorization. In fact, I specified AuthorizationType: NONE in the AWS::ApiGatewayV2::Route.

What's wrong with my configuration?


Solution

  • It was really hard to find the error, but I did. A rather silly mistake, but not fully understood why it has to be like this.

    enter image description here