christianvolk's avatar

AWS S3 Presigned Url Error SignatureDoesNotMatch

I use Laravel as an API and it creates a presigned URL for AWS S3. Then i use this generated URL to upload a image directly from my Vue.js frontend. But this does not work. I get Error 403-SignatureDoesNotMatch.

Full Error: The request signature we calculated does not match the signature you provided. Check your key and signing method.

Laravel function:

public function getSecureUrl(Request $request) {
        try {
            $url = Storage::temporaryUrl(
                'test.jpg', now()->addMinutes(60)
            );

            return response([
                'secure-url' => $url
            ]);
        } catch(\Exception $exception) {
            return response([
                'message' => $exception->getMessage()
            ], 400);
        }
    }

Vue.js function which gets the presigned url from Laravel

submitFile() {
      const formData = new FormData();
      formData.append('file', this.Images);
      const headers = { 'Content-Type': 'multipart/form-data' };
      axios.put('https://test-bucket-cvolk.s3.eu-central-1.amazonaws.com/test.jpg?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT6ILWME6MFPK2YYP%2F20220119%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20220119T182421Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Signature=d05df7544080dbd9a6b71fbc5351bde1b3230e94439236621b64a4d7b2505477', formData, { headers }).then((res) => {
        res.data.files; // binary representation of the file
        res.status; // HTTP status
      });
    }

The URL in the axios method is what i get from Laravel.

What am i doing wrong here?

0 likes
7 replies
Sinnbeck's avatar

You need to format it by adding ``` on the line just before it and just after it :)

Sinnbeck's avatar

Be careful with this. I assume that key gives full access to your S3? Any of your users can just take it and use it for whatever they want. Never expose private keys in JS

christianvolk's avatar

@Sinnbeck the visible key is the public key und the presigned url is already expired. so that should not be a problem. i already created a new public and private key, so these informations are no risk for my s3.

Sinnbeck's avatar

@christianvolk So you expire the presigned url the same second you expose them for the users on the website? In other words, These parameters will work for one upload and one upload only?

christianvolk's avatar

@Sinnbeck i create the presigned url with an expiry date of e.g. 30 seconds (or 60 minutes in the code snippet above). with this created url i upload an image to my private (public access blocked) s3 bucket. after 30 seconds the url is expired. At least that's what i'm trying to do, but s3 responds with an error and the upload fails.

christianvolk's avatar
christianvolk
OP
Best Answer
Level 1

Found a new approach which works perfectly....

Please let me know if something can be written more elegant =)

Laravel:

public function generatePresignedPostUrl(Request $request) {
        $this->validate($request, [
            'filename' => 'string|required'
        ]);

        $s3 = Storage::disk('s3');
        $client = $s3->getDriver()->getAdapter()->getClient();
        $expiry = "+10 minutes";

        $cmd = $client->getCommand('PutObject', [
            'Bucket' => env('AWS_BUCKET'),
            'Key' => 'images/' . $request->filename
        ]);

        $request = $client->createPresignedRequest($cmd, $expiry);

        $presignedUrl = (string)$request->getUri();

        return response()->json(['url' => $presignedUrl], 201);
    }

Vue.js:

async uploadImage(e) {
      if (!e.target.files[0]) return;
      try {
        let imageFile = e.target.files[0];
        let fileName = imageFile.name;
        let formData = new FormData();
        formData.set("filename", fileName);
        let res = await axios.post("/generate-presigned-post-url",formData);
        formData.append("file", imageFile);

        try {
          await axios.put(res.data.url, formData);
        } catch(err) {
          console.log("Failed to upload");
          console.log(err);
        }
      } catch(err) {
        console.log("Failed to upload");
        console.log(err);
      }
    }

Please or to participate in this conversation.