I am encountering a 403 Forbidden error from S3 when uploading files whose filenames include Unicode characters (like ä
). The backend code generates a presigned URL and includes the raw filename in the metadata, while the front-end sends a PUT request with that metadata. This works fine for ASCII-only filenames but fails with Unicode characters, and I only managed to work around it by converting Unicode characters (e.g., turning ä
into a
).
Here’s the backend PHP code that generates the presigned URL:
$userId = ...; // Irrelevant.
$filename = ...; // Comes from POST data.
$safeName = trim(preg_replace('/[^a-z0-9\-_.]/i', '-', $filename), '-'); // AWS only allows specific characters in key.
$key = sprintf('user-documents/%s/%s', $userId, $safeName);
$metadata = [
'type' => 'USER_DOCUMENT',
'userId' => $userId,
'filename' => $filename, // The raw one from POST.
];
$s3 = new S3Client([
'region' => getenv('AWS_REGION'),
'version' => 'latest',
'credentials' => CredentialProvider::env(),
]);
$uploadUrl = $s3->createPresignedRequest(
$s3->getCommand('PutObject', [
'Bucket' => getenv('AWS_BUCKET_USER_DATA'),
'Key' => $key,
'Metadata' => $metadata,
]),
'+1 hour',
)->getUri();
$response = [
'uploadUrl' => $uploadUrl,
'metadata' => $metadata,
];
And here is the frontend JavaScript code that uploads the file to S3:
const file = fileInput.files[0];
const response = await getUploadUrl(file.name); // This is where the POST filename comes from.
await fetch(response.uploadUrl, {
method: 'PUT',
headers: {
'x-amz-meta-type': response.metadata.type,
'x-amz-meta-userid': response.metadata.userId,
'x-amz-meta-filename': response.metadata.filename
},
body: file,
}).then(resp => {
if (!resp.ok) {
throw new Error('File upload failed: ' + resp.status + ' ' + resp.statusText)
}
});
The presigned URL itself contains the URL-encoded original filename, and I expected AWS to decode it correctly. However, when the metadata header contains the Unicode filename, its encoding at the time of the PUT request seems to differ from what was signed, leading to a signature mismatch and therefore a 403 error.
I am looking for guidance on what I need to change in my code to avoid this issue with Unicode filenames. Should I sanitize the filename in the metadata (using the $safeName
) or is there a way to ensure consistent encoding throughout the signing and request process?
aws/aws-sdk-php@3.334.2
might be relevant.