skip to Main Content

I would like to generate a pre signed PUT URL in order to upload images to my S3-Bucket.
This works great with files of type text where the Content-Type:text/plain.

Now I’m facing an issue, when I try the same with images (e.g. dummy.png).

My input parameters

  const bucketParameter: PutObjectCommandInput = {
      Bucket: "myBucket",
      Key: "dummy.png",
      Body: Buffer.from("BODY", "utf-8"),
      ContentType: "image/png",
    };

My code

import { GetObjectCommand, PutObjectCommand, PutObjectCommandInput, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const AWS = require("aws-sdk");

AWS.config.update({
  accessKeyId: "myAccessKey",
  secretAccessKey: "mySecretKey",
  region: "myRegion",
});

export async function getPresignedPutURL(bucketParameter: PutObjectCommandInput, region: string) {
  const s3Client = new S3Client({ region: region });

  try {
    const command = new PutObjectCommand(bucketParameter);
    const signedUrl = await getSignedUrl(s3Client, command, {
      expiresIn: 3600,
    });
    return signedUrl;
  } catch (err) {
    throw new Error("Error creating presigned PUT URL" + err.message);
  }
}

It generates the pre signed URL and I try to execute PUT with the content of an png.

My request header

Accept: */*
User-Agent: Thunder Client (https://www.thunderclient.com)
Content-Type: image/png

My request body is a binary from a png file.

Error message

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>SignatureDoesNotMatch</Code>
    <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
    <AWSAccessKeyId>AKIATFE5YPEBOFPZJYRN</AWSAccessKeyId>
    <StringToSign>AWS4-HMAC-SHA256
20220818T085341Z
20220818/us-east-1/s3/aws4_request
8fcfeba678fa30bdf6ce03595ecb6e80b5aa5429aa0b5b0c7820489113871295</StringToSign>
    <SignatureProvided>606187f7b354fc07df7e7cb0967a71f701c7df32c50393613e4124d95bce950f</SignatureProvided>
    <StringToSignBytes>41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 0a 32 30 32 32 30 38 31 38 54 30 38 35 33 34 31 5a 0a 32 30 32 32 30 38 31 38 2f 75 73 2d 65 61 73 74 2d 31 2f 73 33 2f 61 77 73 34 5f 72 65 71 75 65 73 74 0a 38 66 63 66 65 62 61 36 37 38 66 61 33 30 62 64 66 36 63 65 30 33 35 39 35 65 63 62 36 65 38 30 62 35 61 61 35 34 32 39 61 61 30 62 35 62 30 63 37 38 32 30 34 38 39 31 31 33 38 37 31 32 39 35</StringToSignBytes>
    <CanonicalRequest>PUT
/dummy.png
X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;X-Amz-Credential=AKIATFE5YPEBOFPZJYRN%2F20220818%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20220818T085341Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=content-length%3Bhost&amp;x-id=PutObject
content-length:17663
host:eu-central-1-radix-iflair.s3.us-east-1.amazonaws.com

content-length;host
UNSIGNED-PAYLOAD</CanonicalRequest>
    <CanonicalRequestBytes>50 55 54 0a 2f 64 75 6d 6d 79 2e 70 6e 67 0a 58 2d 41 6d 7a 2d 41 6c 67 6f 72 69 74 68 6d 3d 41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 26 58 2d 41 6d 7a 2d 43 6f 6e 74 65 6e 74 2d 53 68 61 32 35 36 3d 55 4e 53 49 47 4e 45 44 2d 50 41 59 4c 4f 41 44 26 58 2d 41 6d 7a 2d 43 72 65 64 65 6e 74 69 61 6c 3d 41 4b 49 41 54 46 45 35 59 50 45 42 4f 46 50 5a 4a 59 52 4e 25 32 46 32 30 32 32 30 38 31 38 25 32 46 75 73 2d 65 61 73 74 2d 31 25 32 46 73 33 25 32 46 61 77 73 34 5f 72 65 71 75 65 73 74 26 58 2d 41 6d 7a 2d 44 61 74 65 3d 32 30 32 32 30 38 31 38 54 30 38 35 33 34 31 5a 26 58 2d 41 6d 7a 2d 45 78 70 69 72 65 73 3d 33 36 30 30 26 58 2d 41 6d 7a 2d 53 69 67 6e 65 64 48 65 61 64 65 72 73 3d 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 25 33 42 68 6f 73 74 26 78 2d 69 64 3d 50 75 74 4f 62 6a 65 63 74 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a 31 37 36 36 33 0a 68 6f 73 74 3a 65 75 2d 63 65 6e 74 72 61 6c 2d 31 2d 72 61 64 69 78 2d 69 66 6c 61 69 72 2e 73 33 2e 75 73 2d 65 61 73 74 2d 31 2e 61 6d 61 7a 6f 6e 61 77 73 2e 63 6f 6d 0a 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3b 68 6f 73 74 0a 55 4e 53 49 47 4e 45 44 2d 50 41 59 4c 4f 41 44</CanonicalRequestBytes>
    <RequestId>TP7WQ2EVDH41F2V3</RequestId>
    <HostId>brjgBeKuLLcc7pzrrBMPRNtVp2jO/irnMRTyXPwDm9tKUi2aa/FuIG5AJcYVvcbQs7y3jyAIpJ4=</HostId>
</Error>

Why does it work with text files but not with images?

3

Answers


  1. Chosen as BEST ANSWER

    I‘ve found out that the issue was that I‘ve passed the body parameter to the getSignedUrl function.

    Therefore the URL is only valid for a content with the same content-length and type, but if I do not set the Body Parameter my generated URL works.


  2. Your problem seems to be with Body property of the bucketParameter. You are providing the value of a Buffer with the string "BODY"(https://nodejs.org/api/buffer.html#static-method-bufferfromstring-encoding). This is not an image.

    Change your code like this:

    
    // Add fs
    import fs from "fs";
    
    // Get the contents of an image
    const file = fs.readFileSync('./dummy.png');
    const bucketParameter = {
        Bucket: "myBucket",
        Key: "dummy.png",
        Body: file,
        ContentType: "image/png"
    };
    

    I am making the request using Postman.
    enter image description here

    Try making the request again and let’s see if it works for you.


    This is the full code that I use. Try it exactly like this to see if it works for you.

    index.mjs:

    
    import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
    import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
    import AWS from "aws-sdk";
    import fs from "fs";
    
    AWS.config.update({
      accessKeyId: "{update_here}",
      secretAccessKey: "{update_here}",
      region: "{update_here}",
    });
    
    export async function getPresignedPutURL(bucketParameter, region) {
      const s3Client = new S3Client({ region: region });
      try {
        const command = new PutObjectCommand(bucketParameter);
        const signedUrl = await getSignedUrl(s3Client, command, {
          expiresIn: 3600
        });
        return signedUrl;
      } catch (err) {
        throw new Error(err.message);
      }
    }
    
    const file = fs.readFileSync('./dummy.png');
    const bucketParameter = {
        Bucket: "{updated_here}",
        Key: "dummy.png",
        ContentType: "image/png",
        Body: file
    };
    
    const result = await getPresignedPutURL(bucketParameter, "{update_here}");
    console.log(result)
    

    package.json

    {
      "name": "create-presigned-url",
      "version": "1.0.0",
      "type": "module",
      "dependencies": {
        "@aws-sdk/client-s3": "^3.150.0",
        "@aws-sdk/s3-request-presigner": "^3.150.0",
        "aws-sdk": "^2.1197.0"
      }
    }
    
    Login or Signup to reply.
  3. While this doesn’t directly answer the question, I thought I’d add my experience here as it may help some people. I kept getting this error for my presigned PUT url:

    <?xml version="1.0" encoding="UTF-8"?>
    <Error>
        <Code>AccessDenied</Code>
        <Message>Access Denied</Message>
        <RequestId>5GRSACT6Z47XASRS</RequestId>
        <HostId>DDZEvE4cCPbs0qh2KNh2667KxElg4BxHf5QIsUVfdqjd2c3yXJ5l/7yVRpxPXeZbjS7uos6D8GI=</HostId>
    </Error>
    

    After spending absolutely aaages trying to work out what the problem was with the code, it turned out that the problem was that my lambda didn’t have the "s3:PutObject" permission! Once I added that permission to my cloudformation template and redeployed, it started working. Major face palm moment!

    Anyway, for anyone curious, here is my code for generating the signed URL for uploading a file to S3 (when you have the s3:PutObject permission!):

    const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
    const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
    const s3Client = new S3Client({
      apiVersion: "2006-03-01",
      region: process.env.AWS_REGION,
    });
    
      let putCommand;
      try {
        putCommand = new PutObjectCommand({
          Bucket: "my-s3-bucket-name",
          Key: "/public/path/to/new/file.pdf",
        });
      } catch (err) {
        console.log("failed to create AWS.PutObjectCommand: ", err);
        throw err;
      }
    
      let signedUrlForUpload;
      try {
        signedUrlForUpload = await getSignedUrl(s3Client, putCommand, { expiresIn: 3600 });
        if (signedUrlForUpload === null || signedUrlForUpload === "") {
          throw new Error(`got an error for getSignedUrl for upload ${JSON.stringify(signedUrlForUpload)}`);
        }
      } catch (err) {
        console.log("failed to get signed URL for upload: ", err);
        throw err;
      }
      console.log("got signed URL for the result: ", signedUrlForUpload);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search