1

In a React Native application with an Elixir/Phoenix back end I am attempting to have a user create a photo that will push to an S3 bucket using an AWS presigned url. I am using the Hex package ex_aws to expose the API. While I am relatively new to the Elixir, I have followed the docs and the code generates the upload url without any issues.

Config:

config :ex_aws,
  access_key_id: System.get_env("AWS_ACCESS_KEY_ID"),
  secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"),
s3: [
 scheme: "https://",
 host: "my-bucket.s3.amazonaws.com",
 region: "us-east-1"
]

Function generating upload URL:

def get_presign_url do
uuid = UUID.uuid4()
bucket = "photos"
config = %{region: "us-east-1"}
query_params = [{"ContentType", "image/jpeg"}, {"ACL", "public-read"}]
presign_options = [virtual_host: false, query_params: query_params]

{:ok, url} =
  ExAws.Config.new(:s3, config)
  |> ExAws.S3.presigned_url(:put, bucket, "#{uuid}.jpg", presign_options)

%{upload_url: url, url: get_image_url(bucket, uuid)}
end

     defp get_image_url(bucket, uuid) do
    "http://s3.amazonaws.com/my-bucket/#{bucket}/#{uuid}.jpg"
end

and add to types as object:

 object :presign_url do
 field(:url, non_null(:string))
 field(:upload_url, non_null(:string))
end

I am using GraphQL implemented with Absinthe, I have added to the resolvers as follows:

def presign_url(_, _, _) do
{:ok, Server.Posts.get_presign_url()}
 end

This is the upload URL generated:

        {
      "data": {
        "presignUrl": {
       "url": "http://s3.amazonaws.com/my-bucket/photos/3c55bf4c-               cc9f-4ef4-bb53-f7b4190594e1.jpg", 
    "uploadUrl": "https://my-bucket.s3.amazonaws.com/photos/3c55bf4c-  cc9f-4ef4-bb53-f7b4190594e1.jpg?ACL=public-read&ContentType=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIELM4UKGWPA4JINA%2F20180707%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20180707T192017Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=acl%3Bcontenttype%3Bhost&X-Amz-Signature=f731765ccc12dc497050acf9578dcd375116b75a73cf4069beb6c3a46ed37535"
    }
  }
}

Also these are the services that comprise the ex_awspackage:

   {:ex_aws, "~> 2.0"},
   {:ex_aws_s3, "~> 2.0"},
   {:hackney, "~> 1.9"},
   {:sweet_xml, "~> 0.6"},

I created an IAM user with AmazonS3FullAccess policy attached. I have also tried to send the image with IAM user having full administrative priviledges and the S3 bucket being completely public. I have tried with and without UUID and I have tried taking away the content type in query params. The Secret and Access Keys are exported in an .env file, I have checked that they match with AWS and checked for any white space errors. I have ensured that the .env file is sourced. I have also tried adding the keys directly to the authorization type in Postman. Finally, twice I have deleted and re-created both the IAM user and the S3 bucket all to no avail. Using the Put method as specified, this is the return:

      <?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><MyAccessKey></AWSAccessKeyId>
     <StringToSign>AWS4-HMAC-SHA256
20180707T192017Z
20180707/us-east-1/s3/aws4_request
20d99c7ca8361bdf1a22207490e60f15a24181dbb1a6f3242a5a6fc99580bbf6</StringTo  Sign>
 <SignatureProvided>f731765ccc12dc497050acf9578dcd375116b75a73cf4069beb6c3a46ed37535"</SignatureProvided>
<StringToSignBytes>41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 0a 32 30 31 38 30 37 30 37 54 31 39 32 30 31 37 5a 0a 32 30 31 38 30 37 30 37 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 32 30 64 39 39 63 37 63 61 38 33 36 31 62 64 66 31 61 32 32 32 30 37 34 39 30 65 36 30 66 31 35 61 32 34 31 38 31 64 62 62 31 61 36 66 33 32 34 32 61 35 61 36 66 63 39 39 35 38 30 62 62 66 36</StringToSignBytes>
<CanonicalRequest>PUT
    /photos/3c55bf4c-cc9f-4ef4-bb53-f7b4190594e1.jpg
   ACL=public-read&amp;ContentType=image%2Fjpeg&amp;X-Amz-Algorithm=AWS4-      HMAC-SHA256&amp;X-Amz-Credential=AKIAIELM4UKGWPA4JINA%2F20180707%2Fus-`enter code here`east-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20180707T192017Z&amp;X-Amz-Expires=3600&amp;X-Amz-SignedHeaders=acl%3Bcontenttype%3Bhost
acl:
contenttype:
host:my-bucket.s3.amazonaws.com

acl;contenttype;host
UNSIGNED-PAYLOAD</CanonicalRequest>
    <CanonicalRequestBytes>50 55 54 0a 2f 70 68 6f 74 6f 73 2f 33 63 35 35 62 66 34 63 2d 63 63 39 66 2d 34 65 66 34 2d 62 62 35 33 2d 66 37 62 34 31 39 30 35 39 34 65 31 2e 6a 70 67 0a 41 43 4c 3d 70 75 62 6c 69 63 2d 72 65 61 64 26 43 6f 6e 74 65 6e 74 54 79 70 65 3d 69 6d 61 67 65 25 32 46 6a 70 65 67 26 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 72 65 64 65 6e 74 69 61 6c 3d 41 4b 49 41 49 45 4c 4d 34 55 4b 47 57 50 41 34 4a 49 4e 41 25 32 46 32 30 31 38 30 37 30 37 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 31 38 30 37 30 37 54 31 39 32 30 31 37 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 61 63 6c 25 33 42 63 6f 6e 74 65 6e 74 74 79 70 65 25 33 42 68 6f 73 74 0a 61 63 6c 3a 0a 63 6f 6e 74 65 6e 74 74 79 70 65 3a 0a 68 6f 73 74 3a 73 61 67 61 2d 62 65 74 61 2e 73 33 2e 61 6d 61 7a 6f 6e 61 77 73 2e 63 6f 6d 0a 0a 61 63 6c 3b 63 6f 6e 74 65 6e 74 74 79 70 65 3b 68 6f 73 74 0a 55 4e 53 49 47 4e 45 44 2d 50 41 59 4c 4f 41 44</CanonicalRequestBytes>
    <RequestId>217EC822CDAB80C8</RequestId>
    <HostId>m39utFRVAH61JVNNeYPET1ztudrRWy3qsaIGHl9lh5/2Arjfr1L+EcZ3S8UDqcOEB3hIAP5pMOM=</HostId>
</Error>

I believe there must be a permissions issue on the AWS side , I simply am not seeing , however I have been over the docs and I can't find anything, going on the second day with this , any help is greatly appreciated.

  • `SignatureDoesNotMatch` is **never** a permissions issue. It can only be thrown *before* permissions are actually checked. It means only one of two things: either your secret key is wrong, or the request that was signed is not the same as the request that was actually made. (The service cannot tell the difference, because the signing algorithm uses one-way hashing algorithms.) The canonical request in the error response is essentially telling you about the parameters you should have included in your input to the signing algorithm. – Michael - sqlbot Jul 07 '18 at 21:42
  • 1
    First problem I see: the `Content-Type` header seems to be sent on the wire as `ContentType` and the `X-Amz-Acl` header is being sent as `ACL`, and these are also both empty. Also, these don't go in the query string. They are HTTP headers. – Michael - sqlbot Jul 07 '18 at 21:45
  • That was the issue indeed. While Content-Type was being sent as ContentType, this doesn't seem to effect the request. However you were absolutely correct by stating the params shoud not be in the query string. (I was referencing an example that was incorrect) By simply removing: `query_params: query_params` from: `presign_options = [virtual_host: false, query_params: query_params]` the request was successful. Thank you. – Thomas Johnson Jul 07 '18 at 23:42
  • Note that if you don't set a the header `Content-Type`, S3 will implicitly set the content type of the object to its [default value](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html), which is `binary/octet-stream`, which can cause problems using the object in an application that needs to know the correct value. For example, a web browser may try to download an image, instead of displaying it. If correctly signed, an unexpected/undefined header like `ContentType` will be accepted, but ignored and discarded, by S3. – Michael - sqlbot Jul 08 '18 at 00:15
  • @ThomasJohnson Can you share what the corrected signed endpoint URL looks like without the query params? I'm having similair issues – george Nov 07 '18 at 15:26
  • 1
    @george. `def get_presign_url do uuid = UUID.uuid4() bucket = "photos" config = %{region: "us-east-1"} query_params = [{"Content-Type", "image/jpeg"}, {"ACL", "public-read"}] presign_options = [virtual_host: false] {:ok, url} = ExAws.Config.new(:s3, config) |> ExAws.S3.presigned_url(:put, bucket, "#{uuid}.jpg", presign_options) %{upload_url: url, url: get_image_url(bucket, uuid)} end`. Apologies I missed the alert hence the delay. Note that an AWS example was showing to pass query_params to presign_options causing the breakage. – Thomas Johnson Nov 18 '18 at 23:53
  • Using `put` as the method was critical for me. – Kevin Brown Mar 06 '20 at 02:27

0 Answers0