Amplify-js: Amplify REACT fails to call REST API protected by Cognito User Pool (unauthorized OPTIONS call)

Created on 14 Jun 2019  Â·  2Comments  Â·  Source: aws-amplify/amplify-js

Describe the bug
My Amplify REACT application is calling an existing REST API, protected by Cognito User Pool. API has been created with SAM and works OK from Postman.
SAM code is :

  AWSSessionFeedbackAPI:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Cors: "'*'"
      Auth:
        DefaultAuthorizer: MyCognitoAuthorizer
        Authorizers:
          MyCognitoAuthorizer:
            UserPoolArn: !Ref CognitoUserPoolARN

Amplify code is

async getSessions() {
        let apiName = API_NAME;
        let path = '/session';
        let myInit = { 
            headers: { Authorization: `Bearer ${(await Auth.currentSession()).accessToken.jwtToken}` }
        }
        return await API.get(apiName, path, myInit);
    }

This call fails for two reasons.

1. OPTIONS HTTP call is not sending accessToken

The underlying Axios library issues an OPTIONS call before the GET. SAM / API Gateway creates the OPTIONS verb automatically and protects it with the same authentication as the GET verb.
Unfortunately, the Authorization header is not sent, so the API Gateway rejects that call with 401 unauthorized

Here is a copy a cURL output from the browser's network console

curl 'https://ycmi1l7r54.execute-api.eu-west-1.amazonaws.com/Prod/session' -X OPTIONS -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:67.0) Gecko/20100101 Firefox/67.0' -H 'Accept: */*' -H 'Accept-Language: en,fr;q=0.7,es;q=0.3' --compressed -H 'Access-Control-Request-Method: GET' -H 'Access-Control-Request-Headers: authorization' -H 'Origin: http://localhost:3000' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Referer: http://localhost:3000/session'

(no authorization header is sent)

Workaround 1 : disable authentication on the API OPTIONS verb. Is this safe ? Is this a best practice ?

2. OPTIONS call does not return Access-Control-Allow-Headers value

This is probably a problem to report to the SAM team instead.
The default setup of the API Gateway OPTIONS response adds the following HTTP headers :

  • Access-Control-Allow-Origin : *
  • Access-Control-Allow-Methods: GET,OPTIONS

But, by CORS rules, the browser also expects to receive an explicit authorization to send the Authorization header in the GET call. So OPTIONS must also return :

  • Access-Control-Allow-Headers: authorization

Failure to do so result in a CORS error in the browser :

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://ycmi1l7r54.execute-api.eu-west-1.amazonaws.com/Prod/session. (Reason: missing token ‘authorization’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel).

Workaround 2 : manually reconfigure the API Gateway OPTIONS integration response to add Access-Control-Allow-Headers: authorization to the HTTP headers.

Let me know how to best triage this second one, as I realize this is not related to Amplify.

After applying workaround #1 and workaround #2 manually to the API Gateway configuration, the above Amplify React code works as expected.

To Reproduce
Steps to reproduce the behavior:

  1. Create a CUP protected REST API (happy to provide my SAM template)
  2. CUP : add a OAuth client and resources (Scope)
  3. API Gateway : add scope to the HTTP methods
  4. Test Amplify REACT code as shown above

Expected behavior
amplify should work out of the box (i.e. no manual configuration / tweaking) with REST API protected by Cognito User Pool

Screenshots
n/a

Desktop (please complete the following information):

  • OS: [e.g. iOS] MacOS
  • Browser [e.g. chrome, safari] All
  • Version [e.g. 22] All

Smartphone (please complete the following information):
n/a

Additional context
contact me at [email protected] if you need more details.

API

Most helpful comment

After many searches, I finally found a doc explicitly syaing that CORS pre-flight requests MUST NOT be authenticated.

https://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0

So, looks like 1/ above is not an issue with Amplify. I must configure my SAM template to ensure OPTIONS calls are not protected.

All 2 comments

After many searches, I finally found a doc explicitly syaing that CORS pre-flight requests MUST NOT be authenticated.

https://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0

So, looks like 1/ above is not an issue with Amplify. I must configure my SAM template to ensure OPTIONS calls are not protected.

Given the above (OPTIONS call should not be authenticated) I am closing this issue. Here is a sample SAM template to correctly configure API Gateway

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 30

Parameters:
  CognitoUserPoolARN:
    Type: String
    Default: arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${AWS::Region}_XYZ

Resources:
  AWSSessionFeedbackAPI:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Cors: 
        AllowOrigin: "'*'"
      Auth:
        # DefaultAuthorizer: MyCognitoAuthorizer
        Authorizers:
          MyCognitoAuthorizer:
            UserPoolArn: !Ref CognitoUserPoolARN
      DefinitionBody:
        swagger: "2.0"
        info:
          version: "1.0"
          title: "aws-session-feedback"
        securityDefinitions:
          MyCognitoAuthorizer:
            type: "apiKey"
            name: "Authorization"
            in: "header"
            x-amazon-apigateway-authtype: "cognito_user_pools"
            x-amazon-apigateway-authorizer:
              providerARNs:
              - "arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${AWS::Region}_XYZ"
              type: "cognito_user_pools"
        paths:
          /session:
            get:
              responses: {}
              security:
              - MyCognitoAuthorizer:
                - "Sessionfeedback/Sessions"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AWSSessionFeedbackFunction.Arn}/invocations
                passthroughBehavior: "when_no_match"
                httpMethod: "POST"
                type: "aws_proxy"
            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: "'GET,OPTIONS'"
                      method.response.header.Access-Control-Allow-Headers: "'authorization'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                    responseTemplates:
                      application/json: "{}\n"
                requestTemplates:
                  application/json: "{\n  \"statusCode\" : 200\n}\n"
                passthroughBehavior: "when_no_match"
                type: "mock"


  AWSSessionFeedbackFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: session_feedback/
      Handler: app.handler
      Runtime: python3.7
      Events:
        OneSessions:
          Type: Api 
          Properties:
            RestApiId: !Ref AWSSessionFeedbackAPI
            Path: /session
            Method: get

      Policies:
      - Version: '2012-10-17' # Custom Policy to access DynamoDB 
        Statement:
          - Effect: Allow
            Action:
              - dynamodb:GetItem
              - dynamodb:Query
            Resource: !GetAtt AWSSessionFeedbackDatabase.Arn

  AWSSessionFeedbackDatabase:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: aws-session-feedback
      AttributeDefinitions:
        - AttributeName: pk
          AttributeType: S
        - AttributeName: sk
          AttributeType: S
      KeySchema:
        - AttributeName: pk
          KeyType: HASH
        - AttributeName: sk
          KeyType: RANGE
      BillingMode: PAY_PER_REQUEST

Was this page helpful?
0 / 5 - 0 ratings

Related issues

leantide picture leantide  Â·  3Comments

karlmosenbacher picture karlmosenbacher  Â·  3Comments

TheRealRed7 picture TheRealRed7  Â·  3Comments

rygo6 picture rygo6  Â·  3Comments

shinnapatthesix picture shinnapatthesix  Â·  3Comments