Skip to content

CORS error when trying to upload via presigned url from browser, but not in non-browser environment

0

I'm trying to generate s3 presigned url to upload video/mp4 files to a bucket from react app client in development mode in my localhost. However, I'm getting CORS error. But when I do the put request from a python script it goes through, which establishes that my presigned url is working.

This is how I'm generating presigned url through a lambda:

import os
import boto3
from botocore.exceptions import NoCredentialsError
import uuid
import json

def handler(event, context):
    bucket_name = os.environ['BUCKET_NAME']
    object_name = str(uuid.uuid4())+ ".mp4"
    aws_region = "eu-west-1"
    s3_client = boto3.client('s3', region_name=aws_region)


    try:
        response = s3_client.generate_presigned_url('put_object',
                                                    Params={'Bucket': bucket_name,
                                                            'Key': object_name,
                                                            'ContentType': 'video/mp4'
                                                            },
                                                    ExpiresIn=3600,
                                                    HttpMethod='PUT',
                                                    )
    except NoCredentialsError:
        return {
            'statusCode': 400,
            'body': 'No AWS credentials found'
        }

    return {
        'statusCode': 200,
        'body': json.dumps({
            'upload_url': response,
            'object_name': object_name
        })
    }

This is my how my client react app is making the request:

const uploadFile = async (file: File) => {
        setUploading(true);
        setUploadSuccess(null);

        const formData = new FormData();
        formData.append("file", file);

        try {
            // gets the presigned URL for uploading the file via lambda function url
            const uploadUrl: string = await axios
                .get(
                    "https://somethingsomething.lambda-url.eu-west-1.on.aws/",
                )
                .then((response) => response.data.upload_url);


            // uploads the file to the presigned URL
            const response = await axios.put(uploadUrl, file, {
                headers: {
                    "content-type": file.type,
                },
            });
            
            if (response.status === 200) {
                setUploadSuccess(true);
            } else {
                setError("Upload failed. Please try again.");
                setUploadSuccess(false);
            }
        } catch (error) {
            setError("Upload failed. Please try again.");
            console.error(error);
            setUploadSuccess(false);
        } finally {
            setUploading(false);
        }
    };

This is the CORS configuration on the S3 bucket: Enter image description here Error in Chrome console: Enter image description here Preflight option request and response header: Enter image description here Error in Firefox console: Enter image description here Network tab in Firefox: Enter image description here Error when opened:

Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<AWSAccessKeyId>VERY_SECRET</AWSAccessKeyId>
<StringToSign>AWS4-HMAC-SHA256 20240528T125621Z 20240528/eu-west-1/s3/aws4_request 2570e8fe93a8be2efe198c9b2b47a7106d3d950c9128fd32e8982629236e156b</StringToSign>
<SignatureProvided>043d7a68fbb07be33dd78622d2b3f353eb94bfd401ee090dd958a5d1f426740e</SignatureProvided>
<StringToSignBytes>41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 0a 32 30 32 34 30 35 32 38 54 31 32 35 36 32 31 5a 0a 32 30 32 34 30 35 32 38 2f 65 75 2d 77 65 73 74 2d 31 2f 73 33 2f 61 77 73 34 5f 72 65 71 75 65 73 74 0a 32 35 37 30 65 38 66 65 39 33 61 38 62 65 32 65 66 65 31 39 38 63 39 62 32 62 34 37 61 37 31 30 36 64 33 64 39 35 30 63 39 31 32 38 66 64 33 32 65 38 39 38 32 36 32 39 32 33 36 65 31 35 36 62</StringToSignBytes>
<CanonicalRequest>GET /4eb1fd5f-7229-4489-aa8d-02eb59111506.mp4 X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=DELETED_STUFF&X-Amz-Date=20240528T125621Z&X-Amz-Expires=3600&X-Amz-Security-Token=VERY_LONG_THING&X-Amz-SignedHeaders=content-type%3Bhost content-type: host:upload-bucket-20240528113027957000000001.s3-eu-west-1.amazonaws.com content-type;host UNSIGNED-PAYLOAD</CanonicalRequest>
<CanonicalRequestBytes>47 45 54 0a 2f 34 65 62 31 66 64 35 66 2d 37 32 32 39 2d 34 34 38 39 2d 61 61 38 64 2d 30 32 65 62 35 39 31 31 31 35 30 36 2e 6d 70 34 0a 58 2d 41 6d 7a 2d 41 6c 67 6f 72 69 74 68 6d 3d 41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 26 58 2d 41 6d 7a 2d 43 72 65 64 65 6e 74 69 61 6c 3d 41 53 49 41 58 49 52 51 43 47 53 44 4d 4c 46 59 32 59 52 45 25 32 46 32 30 32 34 30 35 32 38 25 32 46 65 75 2d 77 65 73 74 2d 31 25 32 46 73 33 25 32 46 61 77 73 34 5f 72 65 71 75 65 73 74 26 58 2d 41 6d 7a 2d 44 61 74 65 3d 32 30 32 34 30 35 32 38 54 31 32 35 36 32 31 5a 26 58 2d 41 6d 7a 2d 45 78 70 69 72 65 73 3d 33 36 30 30 26 58 2d 41 6d 7a 2d 53 65 63 75 72 69 74 79 2d 54 6f 6b 65 6e 3d 49 51 6f 4a 62 33 4a 70 5a 32 6c 75 58 32 56 6a 45 48 55 61 43 57 56 31 4c 58 64 6c 63 33 51 74 4d 53 4a 48 4d 45 55 43 49 51 43 4e 4d 55 72 61 55 58 62 36 45 75 30 6d 75 63 4e 48 32 4a 66 70 79 4a 4f 51 53 61 36 64 74 56 6c 64 6e 75 64 34 69 4c 25 32 42 25 32 46 4d 41 49 67 63 74 6d 50 34 75 52 45 44 56 58 77 54 52 66 64 39 4e 30 68 63 47 52 76 37 49 55 63 75 70 4e 64 30 49 6a 6f 74 32 33 65 49 65 38 71 25 32 46 77 49 49 37 76 25 32 46 25 32 46 25 32 46 25 32 46 25 32 46 25 32 46 25 32 46 25 32 46 25 32 46 25 32 46 41 52 41 42 47 67 77 30 4f 54 6b 7a 4f 54 41 33 4e 54 55 35 4e 7a 51 69 44 4e 32 51 32 65 6b 52 79 34 30 41 64 7a 53 59 35 69 72 54 41 70 33 45 6d 73 62 62 49 62 77 50 79 4e 42 53 69 79 25 32 46 34 6c 31 37 68 58 4e 6f 4e 4e 79 56 4f 72 54 67 57 5a 6b 65 74 31 30 52 68 79 33 44 5a 42 34 4a 63 53 65 31 4a 55 50 54 62 79 66 59 42 6a 38 47 43 54 76 71 39 64 7a 57 4c 76 43 25 32 46 44 52 4d 70 25 32 42 4d 43 68 64 69 4d 47 4d 54 25 32 42 77 47 79 34 4f 31 58 74 34 66 38 32 67 62 67 4a 41 61 54 68 63 41 55 55 52 4a 65 4a 6e 74 4f 25 32 42 44 72 30 58 6a 33 25 32 46 63 72 79 5a 47 59 44 67 4e 4e 64 33 47 4f 76 74 41 45 25 32 46 76 59 32 76 4e 34 50 68 4b 38 35 6c 35 73 46 51 41 70 45 58 53 42 65 72 76 34 57 46 75 76 43 73 63 74 61 55 77 6e 42 61 69 62 64 4e 6e 36 33 4b 54 34 79 50 76 70 55 45 70 37 4e 4e 31 6c 6a 51 25 32 42 67 71 63 4d 6c 78 4f 42 6c 58 44 66 67 41 76 56 38 43 41 72 6a 49 4f 49 74 6c 50 37 77 36 25 32 42 31 41 6b 46 6f 49 71 54 47 45 45 59 54 31 4a 34 61 6f 25 32 46 43 4b 68 76 50 70 73 32 50 4e 48 69 53 66 52 36 6e 37 72 77 71 75 79 39 58 4b 37 53 38 54 4e 41 4e 57 55 74 30 41 66 73 67 71 56 33 6d 54 50 69 53 37 6e 61 44 79 46 34 63 66 75 56 4e 34 69 75 44 65 73 55 77 36 68 30 49 51 51 38 34 4a 4d 51 56 77 71 35 54 59 41 33 75 42 30 6c 62 64 53 44 50 25 32 46 56 32 58 50 31 44 4d 76 6c 45 51 53 63 75 65 37 43 41 69 6b 46 31 72 69 39 4d 7a 46 41 65 6e 6d 32 25 32 46 35 69 61 35 68 71 35 4b 56 58 59 25 32 42 72 6b 59 76 5a 38 4e 63 59 32 59 67 49 25 32 46 61 47 39 39 6b 46 31 4e 63 78 46 6d 67 7a 5a 44 76 6a 54 6b 46 4e 31 66 6e 7a 41 54 54 44 63 71 4e 65 79 42 6a 71 65 41 62 6f 63 71 36 67 36 73 44 75 7a 6a 32 6f 46 4a 57 72 4b 54 34 39 54 54 51 6d 46 38 38 72 71 37 5a 70 54 38 31 33 77 35 51 53 4e 63 71 69 45 71 6c 47 44 70 65 4f 48 48 4b 4b 69 33 79 53 4a 59 61 35 4c 68 77 52 76 4c 7a 48 53 65 51 4e 58 67 48 41 53 36 63 71 47 52 36 4f 32 44 47 46 6c 6d 41 55 4f 42 56 56 78 6c 66 56 70 79 73 47 4f 77 6d 70 43 51 78 56 77 6a 71 47 56 56 25 32 42 4d 66 76 5a 51 67 77 4c 6c 44 48 78 67 62 57 38 33 4b 63 56 69 79 69 71 35 71 4d 36 63 6f 4a 53 50 48 30 45 25 32 42 6f 70 47 47 6f 74 64 76 6c 57 7a 47 4f 57 4f 56 42 72 65 51 65 37 76 25 32 42 76 55 78 6c 34 72 67 71 50 72 6d 66 35 6a 44 64 59 35 4c 72 76 4c 57 35 70 26 58 2d 41 6d 7a 2d 53 69 67 6e 65 64 48 65 61 64 65 72 73 3d 63 6f 6e 74 65 6e 74 2d 74 79 70 65 25 33 42 68 6f 73 74 0a 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3a 0a 68 6f 73 74 3a 75 70 6c 6f 61 64 2d 62 75 63 6b 65 74 2d 32 30 32 34 30 35 32 38 31 31 33 30 32 37 39 35 37 30 30 30 30 30 30 30 30 31 2e 73 33 2d 65 75 2d 77 65 73 74 2d 31 2e 61 6d 61 7a 6f 6e 61 77 73 2e 63 6f 6d 0a 0a 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3b 68 6f 73 74 0a 55 4e 53 49 47 4e 45 44 2d 50 41 59 4c 4f 41 44</CanonicalRequestBytes>
<RequestId>7PHS4PZNPA18GFS4</RequestId>
<HostId>/cHZxngv3GO+07fX3h7TK1PkIF0bAG6J9lEZ3404FrVpCVNkT262siMpSMevnOqqgiZZ7NpWRFQ=</HostId>
</Error>

Python code that uploads without any issue:

import requests

def upload_file_to_s3(file_path, presigned_url):
    with open(file_path, 'rb') as file:
        try:
            response = requests.put(presigned_url, data=file)
            if response.status_code == 200:
                print("File uploaded successfully.")
            else:
                print(f"Failed to upload file. Status code: {response.status_code}")
                print(response.text)
        except Exception as e:
            print(f"An error occurred: {e}")

if __name__ == "__main__":
    file_path = 'file_example_MP4_480_1_5MG.mp4'  
    presigned_url = "MANUALLY_COPY_PASTED_URL"
    upload_file_to_s3(file_path, presigned_url)
2 Answers
1

I found the solution here:

https://stackoverflow.com/questions/56517156/s3-presigned-url-works-90-minutes-after-bucket-creation

import os
import boto3
from botocore.exceptions import NoCredentialsError
import uuid
import json
from botocore.client import Config


def handler(event, context):
    region = "eu-west-1"
    bucket_name = os.environ['BUCKET_NAME']
    object_name = str(uuid.uuid4())+ ".mp4"
    s3_client = boto3.client('s3', config=Config(signature_version='s3v4'), region_name=region,
                             endpoint_url=('https://s3.'+ region + '.amazonaws.com'))


    try:
        response = s3_client.generate_presigned_url(ClientMethod='put_object',
                                                    Params={'Bucket': bucket_name,
                                                            'Key': object_name,
                                                            'ContentType': 'video/mp4' },
                                                    ExpiresIn=3600,
                                                    HttpMethod='PUT',
                                                    )
    except NoCredentialsError:
        return {
            'statusCode': 400,
            'body': 'No AWS credentials found'
        }

    return {
        'statusCode': 200,
        'body': json.dumps({
            'upload_url': response,
            'object_name': object_name
        })
    }
answered 2 years ago
  • Did you finally solve the issue with this solution?

0

Hey, how about modifying your Lambda function that generates the presigned URL to include CORS headers in its response like this:

return {
    'statusCode': 200,
    'headers': {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, PUT, POST, DELETE, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type'
    },
    'body': json.dumps({
        'upload_url': response,
        'object_name': object_name
    })
}

Let me know if that fix the issue

EXPERT
answered 2 years ago
  • that wound't really make any difference as the lambda function executes and it generates and returns the presigned url successfully, it's only when i try sending put request to the the presigned url from the same client, the cors issue occur

    but the same url doesn't give any cors error from non browser environment like curl or the last code snippet in my post body

  • Try updating this part of your PUT request to ensure proper header capitalization and check again:

    const uploadResponse = await axios.put(uploadUrl, file, {
        headers: {
            "Content-Type": file.type,
    });
    
  • Additionally, for debugging purposes, can you log the presigned URL when it is received in the GET request before making the PUT request?

    console.log("Presigned URL:", uploadUrl);
  • did it, no change

    const response = await axios.put(uploadUrl, file, {
    				headers: {
    					"Content-Type": file.type,
    				},
    				onUploadProgress: (progressEvent) => {
    					if (progressEvent.total) {
    						const percentCompleted = Math.round(
    							(progressEvent.loaded * 100) / progressEvent.total,
    						);
    						setProgress(percentCompleted);
    					}
    				},
    			});
    

    console logging isn't going to help a whole lot, it's a long url and there is nothing special from there

    what i found odd though, the whole thing starts working after a few hours without any changes to the setup, i mean the presigned url stops giving cors error, btw for reference i have updated the body of the question it now has the exact error i was getting

  • i added some delay and now it works but why

    const uploadFile = async (file: File) => { setUploading(true); setUploadSuccess(null); setProgress(0);

    	try {
    		// gets the presigned URL for uploading the file via lambda function url
    		const uploadUrl: string = await axios
    			.get(
    				"https://imagine-a-lambda-url.on.aws/",
    			)
    			.then((response) => response.data.upload_url);
    
    		// wait some time to simulate the upload
    		await new Promise((resolve) => setTimeout(resolve, 2000));
    
    		// uploads the file to the presigned URL
    		// override cors error
    		const response = await axios.put(uploadUrl, file, {
    			headers: {
    				"Content-Type": file.type,
    			},
    			onUploadProgress: (progressEvent) => {
    				if (progressEvent.total) {
    					const percentCompleted = Math.round(
    						(progressEvent.loaded * 100) / progressEvent.total,
    					);
    					setProgress(percentCompleted);
    				}
    			},
    		});
    
    		if (response.status === 200) {
    			setUploadSuccess(true);
    		} else {
    			setError("Upload failed. Please try again.");
    			setUploadSuccess(false);
    		}
    	} catch (error) {
    		setError("Upload failed. Please try again.");
    		console.error(error);
    		setUploadSuccess(false);
    	} finally {
    		setUploading(false);
    	}
    };
    

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.