Encountered a surprise hiccup while attempting to upload file to AWS S3 using browser (putObject

After successfully obtaining a presigned URL through my Node/Express backend for a putObject request on S3, I attempt to upload the relevant files via the browser.

However, when making the put request through the browser (or Postman), I encounter a 400 or 403 error. As someone new to S3, I am unsure where to find information regarding this issue.

While I am able to acquire the presigned URL correctly, why does my put request for the files associated with the URL fail?

To my knowledge, the bucket that I make requests for is publicly accessible.

I have researched various code walk-through tutorials, S3 documentation, and posts related to this topic.

The frontend code used to request presigned URLs is as follows:

// Returns null or an array of objects in the form of { url : string | null; contentType: string; }
const filesDescription = fileList[+key];

if (filesDescription === undefined || filesDescription === null) continue;

const Key          = `${prefix}/${filesDescription.name}`;
const ContentType  =  filesDescription.type;

const request  =  generateEndpointConfig(APIEndpoints.GET_PRESIGNED_URL, { Key, ContentType, userID });

const res      =  await apiCall(request.METHOD, request.URL, request.DATA);

const url = typeof res.data.signedUrl === "string" ? res.data.signedUrl : null;

presignedUrls.push({url, contentType:  ContentType});

The backend Node/Express code used to obtain the URL is as follows:

const { ContentType, Key } = req.body;

const s3 = new S3({
                accessKeyId: AWS_ACCESS_ID,
                secretAccessKey: AWS_SECRET
            });

const url = await s3.getSignedUrlPromise("putObject",
                {
                    Bucket: AMAZON_AWS_STATIC_BUCKET,
                    ContentType,
                    Key,
                    Expires: 300
                });

return res.status(StatusCodes.OK).json({ signedUrl: url })

Lastly, the put requests made to upload files are as follows:

const presignedUrls = await getPresignedUrls(+idx);
if (presignedUrls === null) return false;

for (const fileIdx in presignedUrls)
{
  const fileList      =  files[idx];
  const uploadConfig  =  presignedUrls[fileIdx];

  if (fileList === null || uploadConfig.url === null) continue;

  const fileToUpload = fileList[fileIdx];

  try
  {
    // Make put request for corresponding file to cloud service
    await axios.put(uploadConfig.url, fileToUpload,
    {
        headers:
        {
            "Content-Type": uploadConfig.contentType
        }
    });
  }

  catch(err)
  {
    return false;
  }

It's also worth mentioning that since this is for authenticated users, an Authorization header in the form of Bearer <TOKEN> is transmitted.

=== Edit ===

This is a sample error response received with status code 403 in Postman:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>AccessDenied</Code>
    <Message>Access Denied</Message>
    <RequestId>...</RequestId>
    <HostId>...</HostId>
</Error>

=== Edit 2 ===

The policy set on the bucket includes:

{
    "Version": "2012-10-17",
    "Id": "<POLICY_ID>",
    "Statement": [
        {
            "Sid": "<STMT_ID>",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <CLOUDFRONT_ORIGIN_ID>"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<S3_BUCKET_NAME>/*"
        },
        {
            "Sid": "<STMT_ID>",
            "Effect": "Allow",
            "Principal": {
                "Federated": [
                    "http://localhost:3000",
                    "https://www.my-website.com/"
                ],
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::<S3_BUCKET_NAME>/*"
        }
    ]
}

The CORS configuration on the bucket is as follows:

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "https://www.my-website.com",
            "http://localhost:3000"
        ],
        "ExposeHeaders": []
    }
]

For clarification, this bucket serves a CloudFront distribution for a CDN network, and access to the bucket is restricted only for GET requests at the domain level.

=== Edit 3 ===

An error occurs during the upload process in the browser:

<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature provided by you. Check your key and signing method.</Message>
<AWSAccessKeyId>...</AWSAccessKeyId>
<StringToSign>GET [A_LONG_NUMBER] /[BUCKET_NAME]/[PREFIX]/[IMAGE_NAME].jpg</StringToSign>
<SignatureProvided>...</SignatureProvided>
<StringToSignBytes>[SOME_BYTES]</StringToSignBytes>
<RequestId>...</RequestId>
<HostId>...</HostId>
</Error>

=== Final Edit ===

Alongside the solution provided by @jarmod, it was discovered that my application config automatically adds an Authorization header to all requests once users are authenticated on the site. Removing this header for calls to S3 resolved the issue successfully in the browser.

Answer №1

Upon signing the putObject URL initially, you provided the AWS credentials for an IAM user (consisting of an access key and secret key). Subsequently, when the S3 pre-signed URL is submitted to S3, the original signer of that URL - your IAM user - will serve as the principal for the upload process. Therefore, it is necessary for either the IAM user principal to possess an IAM policy authorizing s3:PutObject on the specified resource, or for your bucket policy to permit this action for the IAM user principal.

Answer №2

Have you set up a CORS configuration for your S3 bucket? This controls which origins are allowed to access your bucket and which HTTP methods they can use. If not, you may encounter an error in the developer console that says

Access to fetch at 'YOUR-PRESIGNED-URL' from origin 'YOUR-ORIGIN' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource

For more information on basic configuration, visit: https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Show an item in a visual format of a list

Need help displaying a specific object: cars: { number': 1, 'overload': 1,'brand': {'0': {'porsche': [{'price': 10, 'id': 0}],'vw': [{'price': 20, 'id': 1}] ...

Refreshable div element in a Code Igniter-powered web application

I am encountering difficulties with automatically refreshing my div using the CodeIgniter framework. My goal in the code snippet below is to have the particular div with id="lot_info" refresh every 1 second. The div is not refreshing, and an error message ...

Organize object properties based on shared values using JavaScript

Check out the JavaScript code snippet below by visiting this fiddle. var name = ["Ted", "Sarah", "Nancy", "Ted", "Sarah", "Nancy"]; var prodID = [111, 222, 222, 222, 222, 222]; var prodName = ["milk", "juice", "juice", "juice", "juice", "juice ...

What is the best way to send information to another client (user) using socket.io?

I am facing an issue where data is only being appended to the sender's message body instead of all connected clients. I need it to work for every user who is currently connected. After going through the documentation, it suggests that broadcasting co ...

The tablesorter plugin from Jquery isn't functioning properly on my dynamically loaded ajax table

I am having trouble getting Tablesorter to work with my table. I attempted to use .trigger('update') but it did not have the desired effect for me. Instead, I tried using stupidtable and it worked to some extent, but not perfectly (it did not sor ...

Vue.js Issue: Image not properly redirected to backend server

Having an issue with my Vue app connecting to the backend using express. When I attempt to include an image in the request, it doesn't reach the backend properly. Strangely though, if I bypass express and connect directly from the Vue app, everything ...

Unlock the App Store instead of iTunes Store using react-native-version-check

I am currently using react-native-version-check to trigger the opening of the app store or play store if an update is available. However, on iOS it redirects to the iTunes store instead of the desired AppStore location. Below is the code in question: ...

What steps can I take to resolve the error message stating that '. is not recognized as an internal or external command" when attempting to run 'npm install' for the MEAN stack development environment?

Just downloaded the mean stack zip file from mean.io, performed an npm install, and encountered an error after a few minutes. Please refer to the screenshot below. Can anyone advise on what steps I should take next? https://i.stack.imgur.com/200Yj.png np ...

leveraging npm packages in Vue single page applications

I recently developed a Vue.js application using vue-loader and now I am trying to integrate an npm package that I have installed. Here is the code snippet: var x = require('package-name') Vue.use(x) However, I encountered the following ...

Selecting Elements with jQuery OR JavaScript

Question: jQuery attribute selector for multiple values I am facing a query regarding jQuery attribute selection. Here is the code snippet I have: $('input:radio[name=foo]').change(function() { blah(); }); $('input:radio[name=bar] ...

Rejuvenate a just-launched window.open starting from the about:blank

After receiving data from an ajax result, I am trying to open a pdf in a new window. However, the pdf viewer is only displayed if I manually resize the window (using manual hotspot resizing). How can I ensure that the contents display properly in its popu ...

How can you effectively retrieve values in Chakra Core without encountering any memory complications?

I have been studying this example in an effort to develop a basic JavaScript engine that can execute scripts like the zxcvbn library. I thought I had it all figured out, but there are certain parts of the code that still puzzle me. Particularly, I am strug ...

What is the correct method for service injection in Angular 8?

I have encountered an issue while trying to inject a service into my main "App" component. The error message is shown in the screenshot below: constructor(private newsApi: NewsApiService) {} Upon importing the service using the code above, I received the ...

Using the onreadystatechange method is the preferred way to activate a XMLHttpRequest, as I am unable to trigger it using other methods

I have a table in my HTML that contains names, and I want to implement a feature where clicking on a name will trigger an 'Alert' popup with additional details about the person. To achieve this, I am planning to use XMLHttpRequest to send the nam ...

What steps are involved in creating a completely self-contained application that can be deployed without relying on Azure services?

During a meeting with management today, it was mentioned that the goal is to develop a self-contained application that could operate without relying on Azure. Can someone provide more information about how this can be achieved? Our current tech stack incl ...

Generating a linked pledge with AngularJS

I need to create a linked commitment for my service provider: this.$get = function($q, $window, $rootScope) { var $facebook=$q.defer(); $rootScope.$on("fb.load", function(e, FB) { $facebook.resolve(FB); }); $facebook.api = functi ...

Tips for creating an effective planar reflection using Open Graphics Library (OGL) in conjunction with a perspective camera

I'm attempting to create a mirror-like reflection of a WebGL scene on a plane using ogl, similar to how a mirror would behave. I tried implementing a basic version of the three.js reflector but ended up with a distorted image that doesn't accurat ...

Password Field Validation in React

<TextField id="outlined-basic" label="Password" variant="outlined" /> Can anyone assist me in implementing password validation using an onchange function? I am seeking help with the ...

Interacting with a 3D model using the mouse cursor in a three

After stumbling upon the three.js library recently, I've been spending several days experimenting with it. I am eager to incorporate a mouse event into my project similar to this example where the head of the skull follows the cursor. However, I want ...

Navigating through JSON arrays can be achieved by utilizing iteration techniques

I'm having trouble with a script and can't figure out how to make it display in the designated div. It may have something to do with how I'm handling the response. In Chrome devtools, the response looks like this: { "[\"record one& ...