Home > Back-end >  Presigned S3 upload url expires before expiration time
Presigned S3 upload url expires before expiration time

Time:09-16

I have a lambda to generate pre-signed upload URL. Here is the code I use to get URL:

    urlExpiresAt := time.Duration(10) * time.Second
    s3Session := s3.New(awsSession)
    request, _ := s3Session.PutObjectRequest(&s3.PutObjectInput{
        Bucket:      aws.String(awssettings.FileStorageBucketName),
        Key:         aws.String(fmt.Sprintf("%s/%s", *cognitoUser.Username, filename)),
        ContentType: aws.String(uploadContentType),
    })

    uploadUrl, err := request.Presign(urlExpiresAt)

On my front-end I retrieve that URL and immediately try to send upload POST request to it:

    try {
      const {
        data: { fileUploadUrl },
      } = await graphQlClient.query({
        query: gql(getFileUploadUrlQuery),
        variables: {
          filename,
        },
      });

      const formdata = new FormData();
      formdata.append("file", file);
      const ajaxRequest = new XMLHttpRequest();
      ajaxRequest.upload.addEventListener("progress", progressHandler, false);
      ajaxRequest.addEventListener("load", completeHandler, false);
      ajaxRequest.addEventListener("error", errorHandler, false);
      ajaxRequest.addEventListener("abort", abortHandler, false);
      ajaxRequest.open("POST", fileUploadUrl);
      ajaxRequest.send(formdata);
    } catch (err) {
      console.log(err);
    }

for testing purposes I configured my bucket to be publically accessable:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "UploadFiles",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "<bucketARN>/*"
        }
    ]
}

and CORS for testing purposes is configured like this:

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "POST"
        ],
        "AllowedOrigins": [
            "http://localhost:8080"
        ],
        "ExposeHeaders": []
    }
]

CORS pre-flight request returns status 200, however POST request to send file content gets rejected with status 403 and following error:

<Error>
  <Code>AccessDenied</Code>
  <Message>Request has expired</Message>
  <X-Amz-Expires>5</X-Amz-Expires>
  <Expires>2022-09-07T04:14:50Z</Expires>
  <ServerTime>2022-09-13T06:40:18Z</ServerTime>
...
</Error>

reading this error it looks like URL has expired before it was pre-signed which doesn't make sense.

What am I doing wrong here?

CodePudding user response:

I figured out there was 2 issues with my solution:

First, presigned URL generated to use PUT method, not POST

Second, AWSAppSyncClient I use as a GraphQL client caches query results by default so all I needed is to disable caching with fetchPolicy: "network-only"

    const {
        data: { fileUploadUrl },
      } = await graphQlClient.query({
        query: gql(getFileUploadUrlQuery),
        fetchPolicy: "network-only",       // <- THIS LINE
        variables: {
          filename,
        },
      });
  • Related