I would like to upload my image to AWS S3 using a pre-signed url created from backend. So, the process is – after submitting a form from Angular application, I’ll send an API request to Node.js backend for creating a pre-signed url and response back to Angular application. Using the pre-signed url Angular application will send a http request to the pre-signed url along with an image file.
So, at first I created a S3 bucket by adding following CORS policy.
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"PUT"
],
"AllowedOrigins": [
"http://localhost:4200"
],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]
Also, I created a IAM
policy –
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PolicyName",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::<timezone>-<bucket_name >",
"arn:aws:s3:::<timezone>-<bucket_name >/*"
]
}
]
}
Here is the Node.js code that create and send pre-signed upload url –
exports.getS3SignedUrl = async (req, res, next) => {
try {
const s3 = new S3({
apiVersion: '2006-03-01',
accessKeyId: process.env.AWS_ID,
secretAccessKey: process.env.AWS_SECRET,
signatureVersion: 'v4',
region: process.env.AWS_REGION
});
const ext = req.query.fileType ? (req.query.fileType).split('/')[1] : null;
if (ext) {
const Key = `${randomUUID()}.${ext}`;
const s3Params = {
Bucket: process.env.AWS_BUCKET_NAME,
Key,
Expires: 60 * 60,
ContentType: `image/${ext}`
}
const uploadUrl = await s3.getSignedUrl('putObject', s3Params);
return res.status(200).json({
uploadUrl,
Key
});
} else {
return res.status(404).json({
message: 'File not found'
});
}
} catch (error) {
console.log(error);
res.status(500).json({ "error": error });
}
}
Node.js code can create a pre-signed url and send it to Angular frontend. But the issue I am facing with uploading the file to AWS S3. When I execute a http request to the pre-signed url, it returns back a 400 bad request.
Here is the frontend code –
.......
const s3BucketUploadInformation = await
this.getS3SignedUrl(file);
await this.saveFileToS3Bucket(s3BucketUploadInformation.uploadUrl,
file);
.......
// We would not upload files to AWS S3 from the backend, instead of we would ask
// backend to create a AWS signed URL, later from the front end upload the file to
// S3 bucket.
public async saveFileToS3Bucket(uploadUrl: string, file: File) {
const headers = new HttpHeaders({ 'Content-Type': file.type });
const req = new HttpRequest('PUT', uploadUrl, file, { headers: headers, reportProgress: true })
let promise = new Promise((resolve, reject) => {
this.http.request(req)
.toPromise()
.then(
res => { // Success
resolve(res);
},
msg => { // Error
reject(msg);
}
);
});
return promise;
}
// Get AWS S3 signed url from backend.
public getS3SignedUrl(file: File) {
let promise = new Promise((resolve, reject) => {
const fileType: string = encodeURIComponent(file.type);
let apiURL = `${environment.API_ADMIN_URL}content/get-s3-signed-url?fileType=${fileType}`;
// Send API request to get AWS S3 signed URL, and an unique key from backend.
this.http.get(apiURL)
.toPromise()
.then(
res => { // Success
resolve(res);
},
msg => { // Error
reject(msg);
}
);
});
return promise;
}
Here is the actual error I get from the http request that was sent to AWS S3 as a pre-signed upload url –
InvalidArgument
Only one auth mechanism
allowed; only the X-Amz-Algorithm query parameter, Signature query
string parameter or the Authorization header should be
specifiedAuthorizationBearer
–bearer—-requestId—-hostId–