title: How to fix 'SignatureDoesNotMatch' error when trying to upload files to S3 using signed method
body: <p>My node.js express application currently uploads image files through the server to an S3 bucket without problems. All configuration for aws.s3 is fine and I can confirm this works. I am now trying to implement direct uploading to the S3 bucket from the browser, and during the implementation came across a few issues (like CORS etc.), all of which I have resolved. I unfortunately cannot seem to resolve (what I hope is the final issue), "The request signature we calculated does not match the signature you provided. Check your key and signing method.", error code "SignatureDoesNotMatch".</p>
  
  <p>I have looked through every StackOverflow and Google result I can find, and tried every option suggested (too many to mention here), but to no avail. I believe my code is as clean and correct as can be, but would appreciate any guidance on resolving this. This fails both when running on localhost and when running the deployed website hosted on Heroku. Reminder, the image uploading through the server works fine in both.</p>
  
  <p>Let's start with the CORs configuration on the bucket:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"&gt;
  &lt;CORSRule&gt;
      &lt;AllowedOrigin&gt;*&lt;/AllowedOrigin&gt;
      &lt;AllowedMethod&gt;GET&lt;/AllowedMethod&gt;
      &lt;AllowedMethod&gt;POST&lt;/AllowedMethod&gt;
      &lt;AllowedMethod&gt;PUT&lt;/AllowedMethod&gt;
      &lt;AllowedMethod&gt;HEAD&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>The server side code to get the signedUrl:</p>
  
  <pre><code>const aws = require('aws-sdk');
  
  const AWS_S3_BUCKET = process.env.AWS_S3_BUCKET;
  
  aws.config.update({
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    region: 'eu-west-2'
  });
  
  const s3 = new aws.S3({signatureVersion: 'v4'});
  
  exports.getSignS3 = (req, res, next) =&gt; {
    const fileName = req.query.filename;
    const fileType = req.query.filetype;
  
    const s3Params = {
      Bucket: AWS_S3_BUCKET,
      Key: fileName,
      Expires: 60,
      ContentType: fileType,
      ACL: 'public-read'
    };
  
    s3.getSignedUrl('putObject', s3Params, (err, data) =&gt; {
      if(err){
        return res.end();
      }
      const returnData = {
        signedRequest: data,
        url: `https://${AWS_S3_BUCKET}.s3.eu-west-2.amazonaws.com/${fileName}`
      };
      res.status(200).json(returnData);
    });
  };
  </code></pre>
  
  <p>And finally the client script:</p>
  
  <pre><code>  document.getElementById("file-input").onchange = () =&gt; {
      const files = document.getElementById('file-input').files;
      const file = files[0];
      if(file == null){
        return alert('No file selected.');
      }
      getSignedRequest(file);
    };
  
    function getSignedRequest(file){
      const xhr = new XMLHttpRequest();
      xhr.open('GET', `/image/sign-s3?filename=${encodeURIComponent(file.name)}&amp;filetype=${file.type}`);
      xhr.onreadystatechange = () =&gt; {
        if(xhr.readyState === 4){
          if(xhr.status === 200){
            const response = JSON.parse(xhr.responseText);
            uploadFile(file, response.signedRequest, response.url);
          }
          else{
            alert('Could not get signed URL.');
          }
        }
      };
      xhr.send();
    }
  
    function uploadFile(file, signedRequest, url){
      const xhr = new XMLHttpRequest();
      xhr.open('PUT', signedRequest);
      xhr.onreadystatechange = () =&gt; {
        if(xhr.readyState === 4){
          if(xhr.status === 200){
            document.getElementById('preview').src = url;
            document.getElementById('avatar-url').value = url;
          }
          else{
            alert('Could not upload file.');
          }
        }
      };
      xhr.send(file);
    }
  </code></pre>
  
  <p>The expected results are for there to be a GET request going to the server code to call AWS and get the signed url, which it does. Then an OPTIONS request to AWS using the signedUrl to return valid options, which it does. And then finally there should be a PUT request to push the image up to AWS, using the signedUrl. Unfortunately this fails.  </p>
  
  <p>The response is as follows. I have manually replaced my Access Key and Bucket Name is the following snippet. But confirm that both these are correct. </p>
  
  <pre class="lang-xml prettyprint-override"><code>&lt;Error&gt;
    &lt;Code&gt;SignatureDoesNotMatch&lt;/Code&gt;
    &lt;Message&gt;
      The request signature we calculated does not match the signature you provided. Check your key and signing method.
    &lt;/Message&gt;
    &lt;AWSAccessKeyId&gt;{myIamAwsAccessKeyId}&lt;/AWSAccessKeyId&gt;
    &lt;StringToSign&gt;
      AWS4-HMAC-SHA256 20190511T105642Z 20190511/eu-west-2/s3/aws4_request 4046216f89b127260feadd407160c8e2fc3dd3a5d19096850c7e292f1c884bbf
    &lt;/StringToSign&gt;
    &lt;SignatureProvided&gt;
      f787cc83f3d4152d9682e5670ba725b65e095696649ca2f1eafb0da3513af93e
    &lt;/SignatureProvided&gt;
    &lt;StringToSignBytes&gt;{removed for brevity}&lt;/StringToSignBytes&gt;
    &lt;CanonicalRequest&gt;
      GET /IMG_5279.jpeg Content-Type=image%2Fjpeg&amp;X-Amz-Algorithm=AWS4- 
  HMAC-SHA256&amp;X-Amz-Credential={myIamAwsAccessKeyId}%2F20190511%2Feu- 
  west-2%2Fs3%2Faws4_request&amp;X-Amz-Date=20190511T105642Z&amp;X-Amz- 
  Expires=60&amp;X-Amz-SignedHeaders=host%3Bx-amz-acl&amp;x-amz-acl=public- 
  read host:{myBucketName}.s3.eu-west-2.amazonaws.com x-amz- 
  acl:public-read host;x-amz-acl UNSIGNED-PAYLOAD
    &lt;/CanonicalRequest&gt;
    &lt;CanonicalRequestBytes&gt;{removed for brevity}&lt;/CanonicalRequestBytes&gt;
    &lt;RequestId&gt;05FBF12EBB2816D5&lt;/RequestId&gt;
   &lt;HostId&gt;angoSNMus7AiVIjCtknMCmtTPAJdc+RJ/I7IuTgwPNFiCOa9NgcdCb938bTHkyN2bXlTxfk/5NU=&lt;/HostId&gt;
  &lt;/Error&gt;
  </code></pre>
  
  <p>Any small bit of advice to resolve this would be appreciated.</p>