6
votes

I am developing a feature to upload file to S3 using Angular/presigned url and API Gateway/Lambda to generate presigned url.

My workflow is described as following:

  1. Get selected file from template
  2. Request my api (gateway/Lambda) to generate presigned URL using file name.

const body = { fileName: this.selectedFile.name } const preSignedUrl = await this.http.post('https://xxxxx.execute-api.eu-west-1.amazonaws.com/dev/v1/profile/avatar', body).toPromise();

  1. Post the file in PUT mode on the generated presigned url.

Backend

I am using serverless (Lambda/API gateway) to calculate the presignedUrl.

Lambda

const AWS = require('aws-sdk')
 
 
module.exports.uploadLargeFile = async (event) => {  
 
  console.log('1. Event: ', event.body);
  const reqBodyAsString = event.body || '{}';
  const fileName = JSON.parse(reqBodyAsString).fileName;
 
  return getUploadURL(fileName);
}
 
const getUploadURL = async (fileName) => {
  const s3Params = {
      Bucket: process.env.AVATAR_BUCKET,
      Expires: 60 * 60,
      ACL: 'public-read',
      Key: fileName 
  };
 
  const s3 = new AWS.S3();    
  try {
      const presinedUrl =  await s3.getSignedUrl('putObject', s3Params);
      return utiles.buildResponse(200, {presinedUrl })
  } catch (error) {
      return utiles.buildResponse(500, {error: 'facing some errors' })
 
  }
};

S3 CORS configuration

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Frontend

Template

 <input  type="file" (change)="onFileSelected($event)"/>
 <button (click)="onUpload()" > Upload to S3 </button>  

Component

selectedFile: File = null;  
  constructor(private http: HttpClient ) {}
 
  onFileSelected(event:any){
    this.selectedFile = <File> event.target.files[0]; 
  }
 
  async onUpload() {   
    console.log('1. SelectedFile: ', this.selectedFile); 
    const body = { fileName: this.selectedFile.name }
    const preSignedUrl = await this.http.post('https://xxxxx.execute-api.eu-west-1.amazonaws.com/dev/v1/profile/avatar', body).toPromise();  
    console.log('2. PreSignedURL: ', preSignedUrl)
    console.log('3. Upoloading File (binary) to S3')
 
    const upload = this.http.put(preSignedUrl.presinedUrl, this.selectedFile).toPromise();    
    upload.then(data => {
      console.log('=> ', data )
    }).catch(err => console.log('error: ', err))
  }

Questions

This code works fine when I use postman (posting binary file).

enter image description here

But when I use Angular I got this error :

HttpErrorResponse {headers: HttpHeaders, status: 403, statusText: "Forbidden", url: "https://bucket-photos-10042018-bucket.s3.eu-west-1.ama…Dlt4WBiu0uLfK8b0%2FWmT9xcKz6jOa0KpQxnWMqm4A%3D%3D", ok: false, …}error: "<?xml version="1.0" encoding="UTF-8"?>↵<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>ASIA5PS6D3MHY4DGELXH</AWSAccessKeyId><StringToSign>PUT↵↵image/png↵1593696048↵x-amz-acl:public-read↵x-amz-security-token:IQoJb3JpZ2luX2VjEMX//////////wEaCWV1LXdlc3QtMSJHMEUCIARoXCbv7BKP9dVn5vhETAol3/qS0737isaWI25J1Uq+AiEAyzIe32BN9KfxcJhg1V5tZnxy6OatGUcOP7zXB0b2Wa8q6gEIXRABGgw5MjY4MzY1NzkwODciDGqkpU2I2+WNMsxTNCrHATPQ4MNNbrijo7GDZ/2zeUYgvBHshXkQQvcNVTBgD0PhKoSJ7VZsCa48J0aYAFXrpsGLrqZeXZkZjAhMxnXUGTFH3ymC2ZFgsHE1V8OOdhbbOSbBeKU7w92WDMNuF8SadrGT/Xl3uHUw/UPVzit5StD15T5sAjTbW4m7SYxRoEFXGq43IN62GOKQDgsaM2MMjSzzEEzY2agcKL8rNlySfWubZuqAKHfplGxz7yRH1uDshSV0uAzwLKdUDi0E1JjwkoyDDOs7mfAwn6L39wU64AGPsqOCEweNrGk1nscphBKiWKYFaht4RQM9Gqt236NXkHQBedxD7Xd3ZJlzpm/dCTyxN75DVLidggrYSTmc8GU4Nl6Rfe3UQ8L4aABSOPgGu5GlA61gelwmPstdTBPgYtzjpoM1ZlNi7MWImslkkpgLqFl1Ls1OtK1alcLF5bghXj6aIEweS6MYVGQiayaCO/0lYh1x7Vyz4A9qiSd5zWO5cwS2jz714p2JljabubYBuWfXhBDpwPYkCA6KXpzvfP3qfDlt4WBiu0uLfK8b0/WmT9xcKz6jOa0KpQxnWMqm4A==↵/dev-inmates-photos-bucket/aws-logo.png</StringToSign><SignatureProvided>JFfuW+hlCyKjZ1vOt3YsxqktikY=</SignatureProvided><StringToSignBytes>50 55 54 0a 0a 69 6d 61 67 65 2f 70 6e 67 0a 31 35 39 33 36 39 36 30 34 38 0a 78 2d 61 6d 7a 2d 61 63 6c 3a 70 75 62 6c 69 63 2d 72 65 61 64 0a 78 2d 61 6d 7a 2d 73 65 63 75 72 69 74 79 2d 74 6f 6b 65 6e 3a 49 51 6f 4a 62 33 4a 70 5a 32 6c 75 58 32 56 6a 45 4d 58 2f 2f 2f 2f 2f 2f 2f 2f 2f 2f 77 45 61 43 57 56 31 4c 58 64 6c 63 33 51 74 4d 53 4a 48 4d 45 55 43 49 41 52 6f 58 43 62 76 37 42 4b 50 39 64 56 6e 35 76 68 45 54 41 6f 6c 33 2f 71 53 30 37 33 37 69 73 61 57 49 32 35 4a 31 55 71 2b 41 69 45 41 79 7a 49 65 33 32 42 4e 39 4b 66 78 63 4a 68 67 31 56 35 74 5a 6e 78 79 36 4f 61 74 47 55 63 4f 50 37 7a 58 42 30 62 32 57 61 38 71 36 67 45 49 58 52 41 42 47 67 77 35 4d 6a 59 34 4d 7a 59 31 4e 7a 6b 77 4f 44 63 69 44 47 71 6b 70 55 32 49 32 2b 57 4e 4d 73 78 54 4e 43 72 48 41 54 50 51 34 4d 4e 4e 62 72 69 6a 6f 37 47 44 5a 2f 32 7a 65 55 59 67 76 42 48 73 68 58 6b 51 51 76 63 4e 56 54 42 67 44 30 50 68 4b 6f 53 4a 37 56 5a 73 43 61 34 38 4a 30 61 59 41 46 58 72 70 73 47 4c 72 71 5a 65 58 5a 6b 5a 6a 41 68 4d 78 6e 58 55 47 54 46 48 33 79 6d 43 32 5a 46 67 73 48 45 31 56 38 4f 4f 64 68 62 62 4f 53 62 42 65 4b 55 37 77 39 32 57 44 4d 4e 75 46 38 53 61 64 72 47 54 2f 58 6c 33 75 48 55 77 2f 55 50 56 7a 69 74 35 53 74 44 31 35 54 35 73 41 6a 54 62 57 34 6d 37 53 59 78 52 6f 45 46 58 47 71 34 33 49 4e 36 32 47 4f 4b 51 44 67 73 61 4d 32 4d 4d 6a 53 7a 7a 45 45 7a 59 32 61 67 63 4b 4c 38 72 4e 6c 79 53 66 57 75 62 5a 75 71 41 4b 48 66 70 6c 47 78 7a 37 79 52 48 31 75 44 73 68 53 56 30 75 41 7a 77 4c 4b 64 55 44 69 30 45 31 4a 6a 77 6b 6f 79 44 44 4f 73 37 6d 66 41 77 6e 36 4c 33 39 77 55 36 34 41 47 50 73 71 4f 43 45 77 65 4e 72 47 6b 31 6e 73 63 70 68 42 4b 69 57 4b 59 46 61 68 74 34 52 51 4d 39 47 71 74 32 33 36 4e 58 6b 48 51 42 65 64 78 44 37 58 64 33 5a 4a 6c 7a 70 6d 2f 64 43 54 79 78 4e 37 35 44 56 4c 69 64 67 67 72 59 53 54 6d 63 38 47 55 34 4e 6c 36 52 66 65 33 55 51 38 4c 34 61 41 42 53 4f 50 67 47 75 35 47 6c 41 36 31 67 65 6c 77 6d 50 73 74 64 54 42 50 67 59 74 7a 6a 70 6f 4d 31 5a 6c 4e 69 37 4d 57 49 6d 73 6c 6b 6b 70 67 4c 71 46 6c 31 4c 73 31 4f 74 4b 31 61 6c 63 4c 46 35 62 67 68 58 6a 36 61 49 45 77 65 53 36 4d 59 56 47 51 69 61 79 61 43 4f 2f 30 6c 59 68 31 78 37 56 79 7a 34 41 39 71 69 53 64 35 7a 57 4f 35 63 77 53 32 6a 7a 37 31 34 70 32 4a 6c 6a 61 62 75 62 59 42 75 57 66 58 68 42 44 70 77 50 59 6b 43 41 36 4b 58 70 7a 76 66 50 33 71 66 44 6c 74 34 57 42 69 75 30 75 4c 66 4b 38 62 30 2f 57 6d 54 39 78 63 4b 7a 36 6a 4f 61 30 4b 70 51 78 6e 57 4d 71 6d 34 41 3d 3d 0a 2f 64 65 76 2d 69 6e 6d 61 74 65 73 2d 70 68 6f 74 6f 73 2d 62 75 63 6b 65 74 2f 61 77 73 2d 6c 6f 67 6f 2e 70 6e 67</StringToSignBytes><RequestId>DD6A5C2957A647FF</RequestId><HostId>pXY2D5gEXY9+G81Tq/VwTEdMk3zTq4fCAsRmAjhneCgpt2buFQwxGScbjASC4vHfDSH/eK8mlGg=</HostId></Error>"headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}message: "Http failure response for https://bucket-photos-10042018-bucket.s3.eu-west-1.amazonaws.com/aws-logo.png?AWSAccessKeyId=ASIA5PS6D3MHY4DGELXH&Expires=1593696048&Signature=JFfuW%2BhlCyKjZ1vOt3YsxqktikY%3D&x-amz-acl=public-read&x-amz-security-token=IQoJb3JpZ2luX2VjEMX%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCWV1LXdlc3QtMSJHMEUCIARoXCbv7BKP9dVn5vhETAol3%2FqS0737isaWI25J1Uq%2BAiEAyzIe32BN9KfxcJhg1V5tZnxy6OatGUcOP7zXB0b2Wa8q6gEIXRABGgw5MjY4MzY1NzkwODciDGqkpU2I2%2BWNMsxTNCrHATPQ4MNNbrijo7GDZ%2F2zeUYgvBHshXkQQvcNVTBgD0PhKoSJ7VZsCa48J0aYAFXrpsGLrqZeXZkZjAhMxnXUGTFH3ymC2ZFgsHE1V8OOdhbbOSbBeKU7w92WDMNuF8SadrGT%2FXl3uHUw%2FUPVzit5StD15T5sAjTbW4m7SYxRoEFXGq43IN62GOKQDgsaM2MMjSzzEEzY2agcKL8rNlySfWubZuqAKHfplGxz7yRH1uDshSV0uAzwLKdUDi0E1JjwkoyDDOs7mfAwn6L39wU64AGPsqOCEweNrGk1nscphBKiWKYFaht4RQM9Gqt236NXkHQBedxD7Xd3ZJlzpm%2FdCTyxN75DVLidggrYSTmc8GU4Nl6Rfe3UQ8L4aABSOPgGu5GlA61gelwmPstdTBPgYtzjpoM1ZlNi7MWImslkkpgLqFl1Ls1OtK1alcLF5bghXj6aIEweS6MYVGQiayaCO%2F0lYh1x7Vyz4A9qiSd5zWO5cwS2jz714p2JljabubYBuWfXhBDpwPYkCA6KXpzvfP3qfDlt4WBiu0uLfK8b0%2FWmT9xcKz6jOa0KpQxnWMqm4A%3D%3D: 403 Forbidden"name: "HttpErrorResponse"ok: falsestatus: 403statusText: "Forbidden"url: "https://bucket-photos-10042018-bucket.s3.eu-west-1.amazonaws.com/aws-logo.png?AWSAccessKeyId=ASIA5PS6D3MHY4DGELXH&Expires=1593696048&Signature=JFfuW%2BhlCyKjZ1vOt3YsxqktikY%3D&x-amz-acl=public-read&x-amz-security-token=IQoJb3JpZ2luX2VjEMX%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCWV1LXdlc3QtMSJHMEUCIARoXCbv7BKP9dVn5vhETAol3%2FqS0737isaWI25J1Uq%2BAiEAyzIe32BN9KfxcJhg1V5tZnxy6OatGUcOP7zXB0b2Wa8q6gEIXRABGgw5MjY4MzY1NzkwODciDGqkpU2I2%2BWNMsxTNCrHATPQ4MNNbrijo7GDZ%2F2zeUYgvBHshXkQQvcNVTBgD0PhKoSJ7VZsCa48J0aYAFXrpsGLrqZeXZkZjAhMxnXUGTFH3ymC2ZFgsHE1V8OOdhbbOSbBeKU7w92WDMNuF8SadrGT%2FXl3uHUw%2FUPVzit5StD15T5sAjTbW4m7SYxRoEFXGq43IN62GOKQDgsaM2MMjSzzEEzY2agcKL8rNlySfWubZuqAKHfplGxz7yRH1uDshSV0uAzwLKdUDi0E1JjwkoyDDOs7mfAwn6L39wU64AGPsqOCEweNrGk1nscphBKiWKYFaht4RQM9Gqt236NXkHQBedxD7Xd3ZJlzpm%2FdCTyxN75DVLidggrYSTmc8GU4Nl6Rfe3UQ8L4aABSOPgGu5GlA61gelwmPstdTBPgYtzjpoM1ZlNi7MWImslkkpgLqFl1Ls1OtK1alcLF5bghXj6aIEweS6MYVGQiayaCO%2F0lYh1x7Vyz4A9qiSd5zWO5cwS2jz714p2JljabubYBuWfXhBDpwPYkCA6KXpzvfP3qfDlt4WBiu0uLfK8b0%2FWmT9xcKz6jOa0KpQxnWMqm4A%3D%3D"__proto__: HttpResponseBase

Could you please help me to understand from where this error is comming ?

it looks like something missing in the request to match the signature ?

Thank you in advance for your help.

1
This question itself is a good help for meLenzman

1 Answers

5
votes

Finaly, I found the reason why it was not working :) The contentType was missing during the generation of presignedUrl;

const s3Params = {
    Bucket: process.env.AVATAR_BUCKET,
    Expires: 60 * 60,
    ACL: 'public-read',
    Key: fileName,
    ContentType: 'image/jpeg' // need to be done dynamically
};