AWS S3 Bucket - getSignedUrl PUT return a 400 (Bad Request)

Viewed 4132

i got a weird issue in my project were i can't manage to found what i did wrong. I am trying to upload an image to my blog and i am using the aws s3 config in order to do it. I believe that my configs are corrects but just in case i will add them there :

here my cors config

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>http://localhost:3000</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

all my IAM policy and user api are in order and activated. Here is the code in my backend :

const s3 = new AWS.S3({
  accessKeyId: keys.aws.clientID,
  secretAccessKey: keys.aws.clientSecret,
  signatureVersion: "v4",
  region: "eu-west-3"
})

const router = express.Router()
// @route  GET api/posts/upload
// @desc   Upload an image on amazone server API
// @access Private
router.get(
  "/upload",
  passport.authenticate("jwt", { session: false }),
  (req, res) => {
    const key = `${req.user.id}/${uuid()}.jpeg`
    s3.getSignedUrl(
      "putObject",
      {
        Bucket: "bebeyogini",
        ContentType: "image/jpeg",
        Key: key
      },
      (err, url) => res.send({ key, url })
    )
  }
)

When i call the /upload endpoint it return a

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

that why in my frontend (in redux) i added a PUT where i am adding the content-type file in the headers. But still it doesn't work in this project

export const sendPost = (newPost, file, history) => async dispatch => {
  dispatch(loading())
  const uploadConfig = await axios.get("/api/posts/upload")
  console.log(uploadConfig)
  console.log(uploadConfig.data)
  await axios.put(uploadConfig.data.url, file, {
    headers: {
      "Content-Type": file.type
    }
  })
  const res = await axios.post("/api/posts", {
    ...newPost,
    imageUrl: uploadConfig.data.key
  })
  history.push("/dashboard")
  dispatch({
    type: POSTS_FETCHED,
    payload: res.data
  })
}

I succedded few month ago in an other project in make the aws s3 working doing these same config and the only difference i had between both is that the one that worked out was setup with cookie session and this one is a jwt-token in the header.

If anyone have an idea... I am completly stuck! here the repository

errors status:

bebeyogini.s3.eu-west-3.amazonaws.com/5bc0eca10743075fecb360f2/8bb4ff60-d8e7-11e8-ad9d-f776f418fd42.jpeg?Content-Type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIAMYTIX267RJ5E4A%2F20181026%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20181026T062242Z&X-Amz-Expires=900&X-Amz-Signature=943652af4bea7e8e0e6d0f97503ea997817e7d68e0540c599643d612d71fe693&X-Amz-SignedHeaders=host:1 PUT https://bebeyogini.s3.eu-west-3.amazonaws.com/5bc0eca10743075fecb360f2/8bb4ff60-d8e7-11e8-ad9d-f776f418fd42.jpeg?Content-Type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIAMYTIX267RJ5E4A%2F20181026%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20181026T062242Z&X-Amz-Expires=900&X-Amz-Signature=943652af4bea7e8e0e6d0f97503ea997817e7d68e0540c599643d612d71fe693&X-Amz-SignedHeaders=host 400 (Bad Request)
createError.js:17 Uncaught (in promise) Error: Request failed with status code 400
    at createError (createError.js:17)
    at settle (settle.js:19)
    at XMLHttpRequest.handleLoad (xhr.js:78)

https://github.com/erwanriou/bebeyogini

2 Answers

Ok after few night of suffering, i did found the solution of the probleme. Be aware that AWS S3 doesn't accept jwt bearer token included in his PUT request. So you have to disable it when doing the request. Here i could fix it on my frontside using the library axios...

export const sendPost = (values, file, history) => async dispatch => {
  dispatch(loading())
  const uploadConfig = await axios.get("/api/posts/upload")
  delete axios.defaults.headers.common["Authorization"]
  await axios.put(uploadConfig.data.url, file, {
    headers: {
      ContentType: file.type
    }
  })
  const token = localStorage.getItem("jwtToken")
  axios.defaults.headers.common["Authorization"] = token
  const res = await axios.post("/api/posts", {
    ...values,
    imageUrl: uploadConfig.data.key
  })
  dispatch({
    type: POSTS_FETCHED,
    payload: res.data
  })
  history.push("/dashboard")
}

The request signature we calculated does not match the signature you provided. Check your key and signing method. this mean your pre-signing data not match with your data what you want to upload.

Your signed data:

{
    Key: `${req.user.id}/${uuid()}.jpeg`
    ContentType: "image/jpeg",
}

Your file meta:

{
    Name: file.name
    ContentType: "image/jpeg",
}

file.name !== ${req.user.id}/${uuid()}.jpeg

You can change the file name in client side to the same file name (what has been generated) on server side, or use opposite way

Related