odysseyscience / react-s3-uploader

React component that renders an <input type="file"/> and automatically uploads to an S3 bucket

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Upload error: Upload error: 403 — `SignatureDoesNotMatch`

gastongouron opened this issue · comments

commented

Hello (and thank you very much for this awesome piece of code react-s3-uploader seems to be), I hope this is not a code related issue, otherwise i'm terribly sorry to waste anyone time:

The context:

  1. Rails API using aws-sdk through the 'aws-sdk', '~> 3' gem
  2. ReactJS client talking to amazon via the react-s3-uploader package

So everything seems to work flawlessly when I upload a file manually via rails, so I believe my rails setup is ok, it defines the following ENV vars to talk to amazon: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, S3_BUCKET.

The backend code is very minimal at this point but is supposed to do the trick for a simple upload test via react, this code is beeing called when the client hits the route /s3/signed_url with the filename as a parameter.

def sign
    filename = params["objectName"]
    content = params["contentType"]

    signer = Aws::S3::Presigner.new
    signed = signer.presigned_url(:put_object,
                     bucket: ENV['AWS_BUCKET'],
                     key: "uploads/#{filename}",
                     acl: 'public-read',
                     content_type: content)

    response.set_header('Access-Control-Allow-Origin', "http://localhost:3000") # as a test
    response.set_header('Access-Control-Allow-Credentials', "true") 
    render json: { signedUrl: signed }
end

On the client side, I use the following ReactS3Uploader:

 <ReactS3Uploader
            signingUrl={`/s3/presigned_url`}
            signingUrlMethod="GET"
            accept="image/*"
            s3path="/uploads"
            preprocess={this.onUploadStart}
            onSignedUrl={this.onSignedUrl}
            onProgress={this.onUploadProgress}
            onError={this.onUploadError}
            onFinish={this.onUploadFinish}
            signingUrlHeaders={ this.headers }
            signingUrlWithCredentials={ true } 
            uploadRequestHeaders={{ 'x-amz-acl': 'public-read' }}
            contentDisposition="auto"
            scrubFilename={(filename) => filename.replace(/[^\w\d_\-.]+/ig, '')}
            inputRef={cmp => this.uploadInput = cmp}
            autoUpload={true}
            /> 

So far the cinematic seems to work good as you can see with the console messages:

  • The user uploads a file
  • Rails in notified and contacts amazon
  • amazon replies with a signed url for the file to be uploaded via a PUT by client
  • Rails responds back to the client with a JSON payload such as signedUrl: the Url
  • The client works based on this response and does an OPTION call which results in 200
  • Wait for it...

** the error ***

  • The clients does a ** PUT with the resource itself that results in a 403**.

screen shot 2018-10-22 at 07 17 45

When visiting the generated url which looks like: https://BUCKETNAME.s3.eu-west-3.amazonaws.com/download.jpeg?x-amz-acl=public-read&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=CREDENTIALSeu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20181022T034739Z&X-Amz-Expires=900&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=8df65f77ade37bf3dfe0f3eb7de829d25b2f73e6316c5ee8b8b15f211dd9a817

I can see a detailed XML error message starting with:

<Error>
  <Code>SignatureDoesNotMatch</Code>
    <Message>
        The request signature we calculated does not match the signature you provided. Check your key and signing method.
    </Message>
     ...

At this point, (I'm stumbling on this 2 days now) I'm truly wondering if the problem lives on the client or server side. I truly believe the server is consistent in his response, signing the data with the correct credentials, filename parameters and path but I might be wrong... :) Since my latest investigations led me to doubt about clearness of amazon error messages in addition of being unsuccessful, I'm wondering if anyone have been facing a similar issue in a similar context (rails backend), and successfully went through it. Also, I'd happily provide more details if it's needed.

Edit1: I also tried with another rails librairy (fog) and the result is the same so rails doesn't seem to be the proble.

Thank you very much for your time <3

commented

The error was coming from the react-s3-uploader component's prop called uploadRequestHeaders. By adding headers, I was making PUT request different from it's original fingerprint, which was not using this header. Just comment this line if you use this package with rails fog example from the README. Solved!

FWIW I had to set the uploadRequestHeaders prop explicitly to {} to avoid this issue, per this comment: #106 (comment)