title: S3 presigned url file upload – nodeJS + client
body: <p><strong>tl;dr</strong> – <code>OPTIONS</code> request made from the browser to a presigned url acquired from a server (AWS S3) gets <code>200</code>, but the following <code>PUT</code> request get <code>403</code></p>
  <p>This is my server-side code responsible for AWS and its S3 service:</p>
  <pre><code>const AWS = require('aws-sdk');
  const config = {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    region: process.env.AWS_BUCKET_REGION,
  };
  AWS.config.update(config);
  
  const S3Bucket = new AWS.S3({
    signatureVersion: 'v4',
    params: { Bucket: process.env.AWS_BUCKET_NAME },
  });
  
  const uploadImage = async (fileData) =&gt; {
    // fileData contains `name` and `type` properties
    const Key = `images/${fileData.name}`;
    const params = {
      Bucket: process.env.AWS_BUCKET_NAME,
      Key,
      Expires: 15 * 60, // 15 minutes
      ContentType: fileData.type,
      ACL: 'public-read',
    };
    const url = await S3Bucket.getSignedUrlPromise('putObject', params);
    return url;
  };
  </code></pre>
  <p>The IAM User from the code above (<code>AWS_ACCESS_KEY_ID</code>, <code>AWS_SECRET_ACCESS_KEY</code>) has the following policy attached:</p>
  <pre><code>{
      &quot;Version&quot;: &quot;2012-10-17&quot;,
      &quot;Statement&quot;: [
          {
              &quot;Sid&quot;: &quot;Stmt1418647210000&quot;,
              &quot;Effect&quot;: &quot;Allow&quot;,
              &quot;Action&quot;: [
                  &quot;s3:Put*&quot;
              ],
              &quot;Resource&quot;: [
                  &quot;arn:aws:s3:::my-actual-bucket-name-here/*&quot;
              ]
          }
      ]
  }
  </code></pre>
  <p>My actual bucket has no <code>Bucket policy</code> set and has the following CORS configuration:</p>
  <pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
  &lt;CORSConfiguration xmlns=&quot;http://s3.amazonaws.com/doc/2006-03-01/&quot;&gt;
  &lt;CORSRule&gt;
      &lt;AllowedOrigin&gt;*&lt;/AllowedOrigin&gt;
      &lt;AllowedMethod&gt;HEAD&lt;/AllowedMethod&gt;
      &lt;AllowedMethod&gt;GET&lt;/AllowedMethod&gt;
      &lt;AllowedMethod&gt;PUT&lt;/AllowedMethod&gt;
      &lt;AllowedMethod&gt;POST&lt;/AllowedMethod&gt;
      &lt;MaxAgeSeconds&gt;3000&lt;/MaxAgeSeconds&gt;
      &lt;AllowedHeader&gt;*&lt;/AllowedHeader&gt;
  &lt;/CORSRule&gt;
  &lt;/CORSConfiguration&gt;
  </code></pre>
  <p>When I make a request from the browser to my server I get a signed URL in the response. Then I use this URL to perform a put request using the following code:</p>
  <pre><code>// `file` is a File class object (that inherits from `FilePrototype` which inherits from `BlobPrototype` that has the following attributes
  // lastModified: 1556044130023
  ​// name: &quot;profile.jpg&quot;
  ​​// size: 788956
  ​​// type: &quot;image/jpeg&quot;
  ​​// uid: &quot;rc-upload-1578069253604-2&quot;
  ​​// webkitRelativePath: &quot;&quot;
  
  const url = 'https://my-actual-bucket.s3.eu-central-1.amazonaws.com/images/profile.jpg?Content-Type=image%2Fjpeg&amp;X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=MYCREDENTIALCODE%2F20200103%2Feu-central-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20200103T163418Z&amp;X-Amz-Expires=900&amp;X-Amz-Signature=b90ed9cbdfadc6401521f80f5d4a65e7d4182becd392ad274ffbe3405d626055&amp;X-Amz-SignedHeaders=host%3Bx-amz-acl&amp;x-amz-acl=public-read';
  const response = await fetch(url, {
    method: 'PUT',
    body: file,
    headers: {
      'Content-Type': file.type,
    },
  });
  </code></pre>
  <p>In my Network tab I can see an <code>OPTIONS</code> request that ends up returning <code>200</code>, and a following <code>PUT</code> request that ends up with <code>403</code> saying:</p>
  <pre><code>&lt;Error&gt;&lt;Code&gt;AccessDenied&lt;/Code&gt;&lt;Message&gt;Access Denied&lt;/Message&gt;&lt;RequestId&gt;BFBCD6D538B3EB6A&lt;/RequestId&gt;&lt;HostId&gt;BtQuuVJw63Ixvir1ghCu0QLq/FKORNSIyyIh9AoYhul1TnsaoZZ1V0p/FBooM/0HTNhM7ZSegM8=&lt;/HostId&gt;&lt;/Error&gt;
  </code></pre>
  <p>Any idea what am I doing wrong here?</p>