Search code examples
amazon-web-servicesamazon-s3axiospre-signed-url

Can't upload a video via S3 presigned URLs from JS code but from Postman it works


I have a pre-signed S3 URL and I can't upload a video when I call this link via Axios but I can when I do it via Postman.

Presigned URL creation:

import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { S3Client } from '@aws-sdk/client-s3';

const myClient = new S3Client(myConfig);
const commandParams: PutObjectCommandInput = {
      Bucket: 'placeholder_name',
      Key: 'some_key',
      ContentType: `video/${contentType}`,
    };

const url = await getSignedUrl(myClient, new PutObjectCommand(commandParams);, {
      expiresIn: 3600,
    });

/** URL goes back to the front-end, nextjs app */
return url

The URL starts with our domain name, region, etc., then the video name and all the parameters Amazon creates, the link works as you will see later on.

Then, on the front end when I call that URL like this, it fails:


 uploadVideoViaPresignedUrl(urlFromServer: string, keyFromServer: string, files: File[]) {
   const formData = new FormData();

   for (const file of files) {
      formData.append(keyFromServer, file);
   }

   return axios({
      method: 'PUT',
      url: urlFromServer
      data: formData,
      headers: { 'Content-Type': 'multipart/form-data' },
    });
  }


I tried with and without the headers, and all I get from axios is network_error and in the browser's network tab, this PUT request fails with NS_ERROR_DOM_BAD_URI

But then when I call the same URL in Postman, with 0 extra configurations it succeeds (notice the 200 status code) and it even uploads a new file every time to my S3 bucket (I have checked):

Post man results

So, I think the issue is from my axios code, it's just a PUT request, does it need anything extra or something? any ideas? Thanks.

Edit:

I double-checked the network tab and now I see this: enter image description here


Solution

  • You need to configure CORS on your S3 bucket and whitelist your web application's domain.

    Your CORS policy could look something like this:

    [
        {
            "AllowedHeaders": [
                "*"
            ],
            "AllowedMethods": [
                "PUT"
            ],
            "AllowedOrigins": [
                "https://my-application.example.com"
            ]
        }
    ]
    

    Before issuing a PUT request, your browser would send a separate OPTIONS request, called "preflight", basically asking the server this:

    You're not the server I loaded my JS code from, but the JS code coming from my-application.example.com is asking me to upload some data to you. Could you please double-check I'm not being a confused deputy?

    This way, S3 knows that code loaded from my-application.example.com has a valid use case to upload data to this bucket, and confirms the browser's preflight request.