Dhruvil Amin
Dhruvil Amin

Reputation: 821

AWS S3 presigned URL with metadata

I am trying to create presigned-url using boto3 below

s3 = boto3.client(
    's3', 
    aws_access_key_id=settings.AWS_ACCESS_KEY, 
    aws_secret_access_key=settings.AWS_ACCESS_SECRET, 
    region_name=settings.AWS_SES_REGION_NAME,
    config=Config(signature_version='s3v4')
)
metadata = {
    'test':'testing'
}
presigned_url = s3.generate_presigned_url(
ClientMethod='put_object', 
Params={
    'Bucket': settings.AWS_S3_BUCKET_NAME,
    'Key': str(new_file.uuid),
    'ContentDisposition': 'inline',
    'Metadata': metadata
})

So, after the URL is generated and I try to upload it to S3 using Ajax it gives 403 forbidden. If I remove Metadata and ContentDisposition while creating URL it gets uploaded successfully.

Boto3 version: 1.9.33

Below is the doc that I referring to: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.generate_presigned_url

Upvotes: 16

Views: 21773

Answers (4)

Orez
Orez

Reputation: 159

In boto, you should provide the Metadata parameter passing the dict of your key, value metadata. You don't need to name the key as x-amz-meta as apparently boto is doing it for you now.

Also, I didn't have to pass the metadata again when uploading to the pre-signed URL:

params = {'Bucket': bucket_name, 
          'Key': object_key, 
          'Metadata': {'test-key': value}
         }

response = s3_client.generate_presigned_url('put_object',
                                             Params=params,
                                             ExpiresIn=3600)

I'm using a similar code in a lambda function behind an API

Upvotes: 2

Id10T-ERROR
Id10T-ERROR

Reputation: 505

I found the metadata object needs to be key/value pairs, with the value as a string (example is Nodejs lambda):

const AWS = require('aws-sdk');
const s3 = new AWS.S3();

exports.handler = async (event) => {
    const { key, type, metadata } = JSON.parse(event.body);

    // example
    /*metadata: {
      foo: 'bar',
      x: '123',
      y: '22.4213213'
    }*/

    return await s3.getSignedUrlPromise('putObject', {
        Bucket: 'the-product-uploads',
        Key: key,
        Expires: 300,
        ContentType: type,
        Metadata: metadata
    });
};

Then in your request headers you need to add each k/v explicitly:

await fetch(signedUrl, {
      method: "PUT",
      headers: {
        "Content-Type": fileData.type,
        "x-amz-meta-foo": "bar",
        "x-amz-meta-x": x.toString(),
        "x-amz-meta-y": y.toString()
      },
      body: fileBuffer
    });

Upvotes: 8

Joe Keene
Joe Keene

Reputation: 2351

I was using createPresignedPost and for me I got this working by adding the metadata I wanted to the Fields param like so :-

const params = {
    Expires: 60,
    Bucket: process.env.fileStorageName,
    Conditions: [['content-length-range', 1, 1000000000]], // 1GB
    Fields: {
        'Content-Type': 'application/pdf',
        key: strippedName,
        'x-amz-meta-pdf-type': pdfType,
        'x-amz-meta-pdf-id': pdfId,
    },
};

As long as you pass the data you want, in the file's metadata, to the lambda that you're using to create the preSignedPost response then the above will work. Hopefully will help someone else...

Upvotes: 4

Dhruvil Amin
Dhruvil Amin

Reputation: 821

Yes I got it working, Basically after the signed URL is generated I need to send all the metadata and Content-Dispostion in header along with the signed URL. For eg: My metadata dictionary is {'test':'test'} then I need to send this metadata in header i.e. x-amz-meta-test along with its value and content-dispostion to AWS

Upvotes: 15

Related Questions