Among other things, the Vox Product team makes apps for building custom advertisements. Recently I was building out a new feature for one of our advertising apps which involves uploading an image asset. My coworker Drew had designed our app to output a S3 pre-signed url that allows an image upload to a specific S3 bucket. My task was to use the Fetch API to POST that image to the bucket. To restrict uploads to images, we are using the “content_type_starts_with” option with the value “image/”:
Though we were originally following these instructions from Heroku, I refactored the code to be vanilla JS, as we didn’t need the jQuery libs referenced in that tutorial. My first pass looked like this:
The code above:
- Creates a “FormData” object
- Adds all the AWS data to it
- Adds the file object to the FormData when a file is added to the form input
- Sets the content-type based on the file type
- Triggers a POST request via Fetch to upload the file to S3
This code, unlike the Heroku jQuery version, returns a 403:forbidden error when I used it to upload an image. I spent hours trying to figure out why. The jQuery version used XMLHttpRequest, rather than Fetch; I first thought that might have something to do with the error. With console.logs and debuggers everywhere, I pulled up each version in a different tab and compared the form headers of each request to see if there was anything different. However, everything seemed the same.
Finally, in our Slack channel dedicated to screaming, I typed “HTTP REQUESTS ARE VERY CONFUSING,” which prompted my coworkers Josh and Emily to offer to pair on a video call to see if we could jointly solve the problem.
Together we discovered a solution that is infuriatingly simple. The content-type needs to be set first in the FormData — before the file is appended — so that the server can confirm the file meets the defined rules. The prior code only needed a single change: I had to move line 14 before line 13. Once I made that change, uploads were all 201:success.
Here’s the working code we put in place:
If ever you encounter this issue, I only hope you find this blog post before you reach the point of screaming!