(React-Django) Getting 403 Forbidden for uploading file to google cloud storage using signed url

55 Views Asked by At

I created signed url to upload files (mp3 video file upto 1GB) from the client side directly on cloud storage. But when I try to upload the file, I am getting following error:

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

This is how the URL was generated:

https://storage.googleapis.com/bucket-name/filename?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=something.iam.gserviceaccount.com%2xyz%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20240207T120631Z&X-Goog-Expires=900&X-Goog-SignedHeaders=content-type%3Bhost&X-Goog-Signature=21...

Front end is in react and I am first making a call to get the signedurl and then uploading the file. React code :


      const responseForSignedUrl = await axios.get(
        `${baseUrl}/api/posts/getuploadurl/`
      );
      if (responseForSignedUrl.status !== 200) {
        throw new Error("Failed to obtain signed URL.");
      }

      const signedUrl = responseForSignedUrl.data.url;

      // Upload video file to Cloud Storage using the signed URL
      const videoFormData = new FormData();
      videoFormData.append("file", video_file);

      const uploadResponse = await axios.put(signedUrl, videoFormData, {
        headers: {
          "Content-Type": "video/mp4",
        },
        onUploadProgress: (progressEvent) => {
          const percentCompleted = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          );
          setProgress(percentCompleted);
        },
      });
     
      if (!uploadResponse.ok) {
        throw new Error("Failed to upload video file.");
      }

Backend code for signed url generation:

def generate_upload_signed_url_v4(request):
    """Generates a v4 signed URL for uploading a blob using HTTP PUT.

    Note that this method requires a service account key file. You can not use
    this if you are using Application Default Credentials from Google Compute
    Engine or from the Google Cloud SDK.
    """
    bucket_name = 'production-bucket-name'
    blob_name = 'test1'

    # storage_client = storage.Client()
    current_directory = os.path.dirname(os.path.abspath(__file__))

    # Navigate to the parent directory (folder A)
    parent_directory = os.path.dirname(current_directory)

    # Access file B within folder A
    file_b_path = os.path.join(parent_directory, "service-credentials.json")
    storage_client = storage.Client.from_service_account_json(file_b_path)
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(blob_name)
    print(storage_client)
    url = blob.generate_signed_url(
        version="v4",
        # This URL is valid for 15 minutes
        expiration=datetime.timedelta(minutes=15),
        # Allow PUT requests using this URL.
        method="PUT",
        content_type="video/mp4",
       
    )


    return JsonResponse({'url': url})
  • Tried creating a new service account with valid permissions and then got the key, didnt work. So pretty sure something is wrong with the code.
  • Tried changing content types with application/octet-stream and multipart/form-data Am I missing something in the request headers ?
1

There are 1 best solutions below

2
Vaibhav Rathod On

I found the bug. Using FormData() to form a request will always send request with content type as "multipart/form-data". So even if we create signed url with any other content type, it would fail. I dont know why it also failed for when i used form-data while signed url. Anyway the we just need to send the file, just the video file, while sending the request and not the form.