Upload error: Upload error: 403 — `SignatureDoesNotMatch`
gastongouron opened this issue · comments
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:
- Rails API using aws-sdk through the
'aws-sdk', '~> 3'
gem - 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**.
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
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)