1

I have a React Application (deployed to S3 with CloudFront + custom domain), which talks to a Python Django backend. My front-end application is simple. It asks my backend for a presigned S3 download url for a .wav audio file, and it loads that audio file in an audio player and has a button to download that file. The audio player is working correctly, but for some reason I can't get downloading to work due to CORS errors. Previously, I added the presigned download url as href in <a>, which opens the audio file correctly in another tab, but I want to download the file as example.wav to the users storage, so I need to do a request to the presigned url to download the data first from my React App. It works on localhost but not on my deployed app.

I'm getting the following (classical CORS) error:

.... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I know that this is because S3 should send back the Access-Control-Allow-Origin header, but the CORS settings on my S3 bucket seem correct, and I've tried all kinds of header configurations on my Axios request.

I did see this stackoverflow post, which points to headers being stripped out by CloudFront, but in my case, the browser React App is directly talking to S3, so that shouldn't be an issue.

TL:DR Whatever I try, the audio player on my front-end is working and playing audio, but the download button is giving CORS errors. I've spent a full week trying different things to get this to work, with no luck. Downloading works on localhost, but not when I deploy it.

=========================

These are the important code snippets, assume that awsFileLink is a presigned download URL to the .wav file in S3.

Audio Player:

...
<audio
  onEnded={() => setIsPlaying(!isPlaying)}
  preload="metadata"
  src={awsFileLink}
  ref={audioPlayer}
  onLoadedMetadata={onLoadedMetadata}
>
  <track kind="captions" />
</audio>
...

Audio Download:

const downloadFile = async (downloadUrl: string): Promise<any> => {
  try {
    return await axios.get(downloadUrl, { responseType: 'blob' });
  } catch (e) {
    console.log('Error', e);
    return e;
  }
};

const useDownloadAudioFile = (awsFileLink: string, fileExtension: string) => {
  const fetchAudioFile = useCallback(async () => downloadFile(awsFileLink), [awsFileLink]);

  const onDownloadHandler = useCallback(async () => {
    try {
      const { data } = await fetchAudioFile();

      const url = window.URL.createObjectURL(data);

      const link = document.createElement('a');

      link.href = url;
      link.setAttribute(
        'download',
        `${uuidv4()}.${fileExtension}`,
      );

      // Append to html link element page
      document.body.appendChild(link);

      // Start download
      link.click();

      // Clean up and remove the link
      link.parentNode?.removeChild(link);
    } catch (e) {
      console.log('Error', e);
    }
  }, [fetchAudioFile]);

  return onDownloadHandler;
};

Amazon S3 CORS settings (scoping this down once everything is working):

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET",
            "PUT",
            "POST",
            "HEAD",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 3000
    }
]

Backend Logic:

class S3FileSystem:
    def __init__(self):
        self.client = boto3.client(
            's3',
            settings.AWS_REGION_NAME,
            aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
            aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY
        )


    def get_presigned_url_for_download(self, key, time=3600):
        object_exists = True
        try:
            object_exists_response =  self.client.get_object_attributes(
                    Bucket= settings.AUDIO_FILES_BUCKET_NAME,
                    Key=key,
                    ObjectAttributes =['ObjectSize']
            )
            if object_exists_response is None or object_exists_response["ObjectSize"] == 0:
                object_exists = False
        except Exception:
            object_exists = False

        return object_exists, self.client.generate_presigned_url(
            ClientMethod='get_object', 
            ExpiresIn=time,
            Params={'Bucket': settings.AWS_ASSETS_BUCKET_NAME, 'Key': key},
            HttpMethod='GET',
        )

I've tried multiple CORS setting on my S3 bucket, I've tried Fetch vs Axios, I've tried multiple headers when calling the request.

LJB
  • 11
  • 2

1 Answers1

0

It's worked for me. You can try it. https://www.hacksoft.io/blog/handle-images-cors-error-in-chrome

The solution: Disable cache