androidamazon-web-servicesamazon-cognitoincognito-modeawss3transferutility

How to use TransferUtility with Cognito user / Identity pools in Android


I need to upload one file in Android to an s3 bucket. We want to authenticate the upload with Cognito User pools.

The standard way to do this would be to use the Storage API from the Amazon Amplify SDK.

Problem is that I need to report upload progress for the upload, and Amplify Storage does not allow you to set a listener to observe Progress in Android.

That takes me to the need of using TransferUtility.

How I am supposed to authenticate the upload operation using Cognito with TransferUtility?

At the moment I'm doing:

            val credentials = CognitoCachingCredentialsProvider(
                context,
                "eu-west-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
                Regions.US_WEST_1)

            val s3Client = AmazonS3Client(credentials, Region.getRegion(Regions.US_WEST_1))

            val transferUtility = TransferUtility.builder()
                .context(context)
                .s3Client(s3Client)
                .build()

But I'm getting a 403:

com.amazonaws.services.s3.model.AmazonS3Exception: Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: XXXXXXXXXXX), S3 Extended Request ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx=

I'm really struggling to get any documentation for this, as it seems the Storage API is now preferred over TransferUtility, and everything to do with Cognito user pools or user identities is done with Amplitude Storage


Solution

  • I've finally found some documentation do to this. Basically, you use AWSMobileClient as the implementation for the AWSConfiguration with building the TransferUtility

    
            TransferUtility transferUtility =
                TransferUtility.builder()
                    .context(getApplicationContext())
                    .awsConfiguration(AWSMobileClient.getInstance().getConfiguration())
                    .s3Client(new AmazonS3Client(AWSMobileClient.getInstance().getCredentialsProvider()))
                    .build();
    
            TransferObserver uploadObserver =
                transferUtility.upload(
                    "s3Folder/s3Key.txt",
                    new File("/path/to/file/localFile.txt"));
    

    https://github.com/awsdocs/aws-mobile-developer-guide/blob/master/doc_source/how-to-transfer-files-with-transfer-utility.rst

    Also, there are two other caveats I had to figure out to make this work without receiving HTTP 403:

    1. Add required information to awsconfiguration.json
    {
      "IdentityManager": {
        "Default": {}
      },
      "CognitoUserPool": {
        "Default": {
          "PoolId": "region_XXXXXXX",
          "AppClientId": "XXXXXXXXXXXXXXX",
          "Region": "XXXXX"
        }
      },
      "CredentialsProvider": {
        "CognitoIdentity": {
          "Default": {
            "PoolId": "XXXXX:XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "Region": "XXXXXX"
          }
        }
      },
      "S3TransferUtility": {
        "Default": {
          "Bucket": "XXXXXXXXXXXXXXXXXXX-data-bucket",
          "Region": "XXXXXX"
        }
      }
    }
    
    

    Note that CredentialsProvider.CognitoIdentity.Default.PoolId is not the same as CognitoUserPool.Default.PoolId!

    1. Prepend the "private"/"public" prefix to the key when performing the upload if required, depending on the server-side configuration.