403 when upload file to S3 bucket using axios - reactjs

I'm using axios to upload an audio file to AWS s3 bucket.
The workflow is: React => AWS API Gateway => Lambda.
Here is the backend Lambda code where generates the S3 presigned URL:
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(AUDIO_S3_BUCKET)
.key(objectKey)
.contentType("audio/mpeg")
.build();
PutObjectPresignRequest putObjectPresignRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(10))
.putObjectRequest(putObjectRequest)
.build();
PresignedPutObjectRequest presignedPutObjectRequest = s3Presigner.presignPutObject(putObjectPresignRequest);
AwsProxyResponse awsProxyResponse = new AwsProxyResponse();
awsProxyResponse.setStatusCode(HttpStatus.SC_OK);
awsProxyResponse.setBody(
GetS3PresignedUrlResponse.builder()
.s3PresignedUrl(presignedPutObjectRequest.url().toString())
.build().toString());
return awsProxyResponse;
Here is the java code to create the bucket:
private void setBucketCorsSettings(#NonNull final String bucketName) {
s3Client.putBucketCors(PutBucketCorsRequest.builder()
.bucket(bucketName)
.corsConfiguration(CORSConfiguration.builder()
.corsRules(CORSRule.builder()
.allowedHeaders("*")
.allowedMethods("GET", "PUT", "POST")
.allowedOrigins("*") // TODO: Replace with domain name
.exposeHeaders("ETag")
.maxAgeSeconds(3600)
.build())
.build())
.build());
log.info("Set bucket CORS settings successfully for bucketName={}.", bucketName);
}
In my frontend, here is the part that try to upload file:
const uploadFile = (s3PresignedUrl: string, file: File) => {
let formData = new FormData();
formData.append("file", file);
formData.append('Content-Type', file.type);
const config = {
headers: {
"Content-Type": 'multipart/form-data; boundary=---daba-boundary---'
//"Content-Type": file.type,
},
onUploadProgress: (progressEvent: { loaded: any; total: any; }) => {
const { loaded, total } = progressEvent;
let percent = Math.floor((loaded * 100) / total);
if (percent < 100) {
setUploadPercentage(percent);
}
},
cancelToken: new axios.CancelToken(
cancel => (cancelFileUpload.current = cancel)
)
};
axios(
{
method: 'post',
url: s3PresignedUrl,
data: formData,
headers: {
"Content-Type": 'multipart/form-data; boundary=---daba-boundary---'
}
}
)
.then(res => {
console.log(res);
setUploadPercentage(100);
setTimeout(() => {
setUploadPercentage(0);
}, 1000);
})
.catch(err => {
console.log(err);
if (axios.isCancel(err)) {
alert(err.message);
}
setUploadPercentage(0);
});
};
However, when try to upload the file, it return 403 error.
And if I use fetch instead of axios instead and it works, like this:
export async function putToS3(presignedUrl: string, fileObject: any) {
const requestOptions = {
method: "PUT",
headers: {
"Content-Type": fileObject.type,
},
body: fileObject,
};
//console.log(presignedUrl);
const response = await fetch(presignedUrl, requestOptions);
//console.log(response);
return await response;
}
putToS3(getPresignedUrlResponse['s3PresignedUrl'], values.selectdFile).then(
(putToS3Response) => {
console.log(putToS3Response);
Toast("Success!!", "File has been uploaded.", "success");
}
);
It seems to me that the only difference between these two is that: when using fetch the request's Content-Type header is Content-Type: audio/mpeg, but when using axios it is Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryClLJS3r5Xetv3rN7 .
How can I make it work with axios? I'm switching to axios for its ability to monitor request progress as I want to show an upload progress bar.
I followed this blog and not sure what I missed: https://bobbyhadz.com/blog/aws-s3-presigned-url-react

You are using POST in your axios. Should be PUT instead.
Also I think the content type has to match the one specified during requesting the pre-signed URL, which is audio/mpeg as you rightly pointed out.
Correspondingly, your data should be just file, instead of formData.
axios(
{
method: 'put',
url: s3PresignedUrl,
data: file,
headers: {
"Content-Type": 'audio/mpeg'
}
}
...

You didn't mark any answers as accepted so I guess you didn't solve it.
For any future viewers out there. The reason why you are getting 403 forbidden error is because your Content-Type in your server and client side are not matching. I'm assuming you set up the AWS policies correctly.
Your code in the backend should look like this:
const presignedPUTURL = s3.getSignedUrl("putObject", {
Bucket: "bucket-name",
Key: String(Date.now()),
Expires: 100,
ContentType: "image/png", // important
});
and in the front-end (assuming you are using axios):
const file = e.target.files[0]
const result = await axios.put(url, file, {
withCredentials: true,
headers: { "Content-Type": "image/png" },
});
In practical, you would normally have to send the file type to generate the pre-signed url in the POST body or whatever and then in axios you do file.type to get the file type of the uploaded file.

Check your Lambda execution role. It may be the culprit. Perhaps it does not grant enough permissions to allow PUTting files into your bucket.
URL signing is a delegation of power on behalf of the signer, which is restricted to a specified object, action... Signing does not magically grants full read/write permissions on S3, even on the specific object related to the presigned URL.
The "user" who generates the signature requires sufficient permissions to allow the actions you want to delegate through that presigned URL. In this case, this is the execution role of your Lambda function.
You can add the AmazonS3FullAccess managed policy to the execution role and see if it solves your situation. This change took me out of a blocked situation me after days of struggle. Afterwards, before going to production, restrict that rule to the specific bucket you want to allow uploads into (least privilege principle).
If you develop using SAM local emulation, those execution roles seem not to be taken into account as long as you run your functions locally; the signed links work in that context even without S3 permissions.

Related

Loading xslx file from server to React

Dear fellow developers!
Tell me how to transfer a file with the .xlsx extension from the Spring backend to the React frontend?
Let's get my code first. This is my backend controller which returns a ByteArrayResource:
#PostMapping("/download-report")
public ResponseEntity<ByteArrayResource> generateReport(#RequestBody ReportAttributesWithFiltersDto attributesWithFilters) {
var resource = reportBuilder.generateReport(attributesWithFilters);
var headers = getReportHeaders();
return new ResponseEntity<>(resource, headers, HttpStatus.CREATED);
}
private HttpHeaders getReportHeaders() {
var contentDisposition = ContentDisposition.builder("attachment")
.filename("attachment; filename=my_file.xlsx")
.build();
var headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "force-download"));
headers.setContentDisposition(contentDisposition);
return headers;
}
The backend performs well, if you test it in the development environment, it will return the file in response.
The file downloads fine and there are no problems. Backend shouldn't be a problem.
The problems start when I upload the file on the frontend.
Here is my request:
downloadReport: builder.mutation({
query: (body) => ({
url: paths.downloadReport,
method: "POST",
body: body,
responseHandler: "blob",
}),
}),
What nonsense I get in response, this is some kind of nightmare!
In the case of using a blob, I get this:
res:
data:
undefined
If I set responseHandler: "text", then I get this:
{
"data": "PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0013\u0000\u0000\u0000[Content_Types].xml�S�n�0\u0010����*6�PU\u0015�C\u001f�\u0016��\u0003\\{�X�%����]\u00078�R�\nq�cfgfW�d�q�ZCB\u0013|��|�*�*h㻆},^�{Va�^K\u001b<4�\u00076�N\u0016�\bXQ�dž�9�\u0007!P��$�\u0010�\u0013҆�d�c�D�j);\u0010��ѝP�g��E�M'O�ʕ����H7L�h���R���G��^�'�\u0003\u0007{\u0013�\b�zސʮ\u001bB��3\u001c�\u000b˙��h.�h�W�жF�\u000ej娄CQՠ똈���}ιL�U:\u0012\u0014D�\u0013����%އ����,�B���[�\t��\u001e ;˱�\t�{N��~��X�p�\u001cykOL�\u0004\u0018�kN�V��ܿBZ~����q\u0018��\u000f �a\u0019\u001fr��{O�\u0001PK\u0007\bz��q;\u0001\u0000\u0000\u001c\u0004\u0000\u0000PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000b\u0000\u0000\u0000_rels/.rels���j�0\f�_���8�`�Q��2�m��\u00014[ILb��ږ���.[K\n\u001b�($}�\u0007�v?�I�Q.���uӂ�h���\u001bx>=��#\u0015��p�H\u0006\"�~�}�\t�n����*\"\u0016\u0003�H�׺؁\u0002��\u0013���8\u0007�Z�^'�#��7m{��O\u0006�3��\u0019�G�\u0006u�ܓ\u0018�'��y|a\u001e�����D�\t��\u000el_\u0003EYȾ�\u0000���vql\u001f3�ML�eh\u0016���*�\u0004��\\3�Y0���oJ׏�\u0003\t:\u0014��^\b�\u001f�}\u0002PK\u0007\b��z��\u0000\u0000\u0000I\u0002\u0000\u0000PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0010\u0000\u0000\u0000docProps/app.xmlM��\n�0\u0010D��\u0010ro�z\u0010�4� �'{�\u000f\b��\u0006�MHV�盓z�\u0019��T��E�1e\u0017�����\u0002Ɇ�ѳ����:�No7jH!bb�Y�\u0007�V���\u0004�����T�)$o���0M��9ؗGb�7�\u0001pe�\u0011�*~�R�>��Y�EB��\u0014�\u0018nW\u0005������\u0000PK\u0007\b6n�!�\u0000\u0000\u0000�\u0000\u0000\u0000PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0011\u0000\u0000\u0000docProps/core.xmlm��J�0\u0010F_�依�EqC�E�\u0005Aq���w!\u0019�b�C\u0012����u���%��\u001c&_�=�1yG�\u0007�kB��$�����j����\u000b������X\u0013mȶ��e�8�;cх\u0001}\u00125�3ak҇`\u0019�\u0017=*�H�\u0018�\u0018�x�Wׁ��w\bE�����%\u000f\u001cfajW#9)�X��͍�#\n�\u0011\u0015���f\u0014~؀N�\u0007�d%�~X�i���\\��\u0011����ò|:���\u0002IS��L8�\u0001e\u0012\u0005,|���w�T^]�;�\u0014yQ��&�EK7����=W�k~\u0016~��k.c!=&����[�+�Ss�\tPK\u0007\bȂ`�\u0005\u0001\u0000\u0000�\u0001\u0000\u0000PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0014\u0000\u0000\u0000xl/sharedStrings.xml���J�#\u0014����0d�Bҙ&��!M�\u001b��ӕ�(5�\u0001��L�.�\".*\u0016ԅ��}�x)F{�\u0015μ�'��4\u0005e`����?�93F��9 G��m�-J�\u0014���V�=ۭ\u0016���5Y�J����y#*^�\rP��Hõ\u000f\u001b��$��\bڸ�(Ղ��S�+5�)�W�\\��{�S\u000e�ӯR^���\u001e�YV�\u001cP��\u001cuʶ+�\u0006�M#0�+�a�+$Ї\b\u000f�8�Р�i�X2��#\b}q=3w\u0007a\u001c'�\u0014F�\u000eo(\u001d�M{Z��B0\u001d�6��'D\t�{\u0018�6\u0011\u0017��G�\b]^g���;Z��#���\b��&�H�#�J\u000f�3q���gl�j:�0E�YAN+[鬮j��R�VP��E����6|\u001c1��x�;ޝ�}scI��Y��-�D\u0013YZ�A\u0010{<\"q..\u0012‡x�\u0005x\u0014MD|\u0013��?\bU=SHi�\\.���P�d\u0013���\u000f�'.q\u000f�7���������,N�\u0019�5��_�\u0014\u001f��\rPK\u0007\b��-��\u0001\u0000\u0000�\u0002\u0000\u0000PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\r\u0000\u0000\u0000xl/styles.xml���n� \u0010��J}\u0007���d���&C%W��J]�9ۨpX#\"�O_0N�L\u001d:����\u001f���n4���ye���UA\t`c�®�\u001f��iKw���aҰ�\u0001\u0002�\u000e�\u0015�C\u0018^\u0018�M\u000fF��\u001d\u0000�Ik�\u0011!��c~p �O&�٦(��\u0011\n)/�dj\u0013<i�\tCE\u000b�x�Z�*k�\u0005^�or\u0016:*i���Xm\u001dQ(a\u0004Y�m�P\u0018�]�B��S3O\u0018��,o�0O��\u0019��%��[��Ii�;Ćf���\b\u0001\u001cֱ K~�\u0006�(Z�������}�91�8�\u0010/>Z'�\u0016nߟ%^jhC48��)\u0006;�t\u0018�51�Jt\u0016�NȋcI\"�\u0001��iu��\u001d{lI���L����_�8ВfL.\u0012�����ƒ����hv���\u000fPK\u0007\b����E\u0001\u0000\u0000�\u0002\u0000\u0000PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000f\u0000\u0000\u0000xl/workbook.xml���N�0\u0010�w$�����\u0001� ��\u0005!uc(�}i��v�3-s\u0017ނ��=�7�I\u0015`d:��������\u001e#��%\u0014\u000b\u0001\f�\u000e��������=,�ˋ�\u0010�n\u0013Žeޓ�6���t�N�\"���\tѩ�c�r�#*C-br\u001d�\u0016�;e=�\re��#4���\u0018��C�Β��J�-��'��=GfT��A�JhTG\b���ͫ�\u0003��cdJ'�ǵ�H\u0010#���S�y2�\u001cJ8}\u000e����5\u001c���\u001a\tqen�M�*�b�̧|~V\u0003PK\u0007\b\u0007$�h�\u0000\u0000\u0000b\u0001\u0000\u0000PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u001a\u0000\u0000\u0000xl/_rels/workbook.xml.rels��Mk�0\f#���}q��\u0018�n/c����\u0000c+qh\"\u0019K�迟���#\u0007;�$��{\u000f��~Γy�\"#���i� \u0005�#\r\u000e^�O7�`D=E?1�\u0003b�n��8y�?$�YLE�8H���Z\t\tg/\rg����^�\f6�p�\u0003�U���r΀%�좃��\u001d��/\u0003�\u0003I�`|�Rˤ��:f����~\f���mF�\u000bv����\u001c�:���ׯ�������p9HB�Sy\u001dݵK~\u0004�\u0018����\u000bPK\u0007\b�\u0003;��\u0000\u0000\u00003\u0002\u0000\u0000PK\u0003\u0004\u0014\u0000\b\b\b\u0000�,U\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0018\u0000\u0000\u0000xl/worksheets/sheet1.xml��[�� \u0018���?\u0010�+��э��vִ\u0017M6=^3����\u0001fܟ_p\fE�l:���<|/�|���e���rѱ1���B#ǒU��d����]\f��oҙ�g�R*�*\u0018E\u0006[)�;�D�ҁ\b�MtT�f| RMy���)����G�u#4�n�yZu\u0003\u001d����:�\u000f�]�C����\u001f\u001d��5\u0006z�\u0013c�z�ʠR�������T�%?S]��(/\u0016�'\u000e*Z�s/���#�V�/\rէ����b�\u0005C�\u000f\u0000���,�sW�V�\u0002'�q�F8��<\u000bɆ�W��\u000bNTȢ��5L\u0016^���:8����?�\u001a�Q��\n\u0012'v�(�9*X�\u0002\u0013�8��q\nנ�>�$H�w�T�fE&\u000b�ο\u001f9�vq���H���̀�6�M��A5T\u0015\b5��n�.�he�m�m�\u0007��-;��߲G�\u0005[V�,4\f)g#��8^\u0016�W9��ݱ�\u0015��mvع�,޹�,ٹ��s�.�\u001by�uy�\u0015y�y��\u001c7pז�\r�����`珬\u001b4��~&��F\u0001NL�;�^\u001eG��1I����V=qf��Z.� ��gf\u0019K6������4�\u0005PK\u0007\b�Q�.�\u0001\u0000\u0000}\u0005\u0000\u0000PK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,Uz��q;\u0001\u0000\u0000\u001c\u0004\u0000\u0000\u0013\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000[Content_Types].xmlPK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,U��z��\u0000\u0000\u0000I\u0002\u0000\u0000\u000b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000|\u0001\u0000\u0000_rels/.relsPK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,U6n�!�\u0000\u0000\u0000�\u0000\u0000\u0000\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000�\u0002\u0000\u0000docProps/app.xmlPK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,UȂ`�\u0005\u0001\u0000\u0000�\u0001\u0000\u0000\u0011\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000i\u0003\u0000\u0000docProps/core.xmlPK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,U��-��\u0001\u0000\u0000�\u0002\u0000\u0000\u0014\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000�\u0004\u0000\u0000xl/sharedStrings.xmlPK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,U����E\u0001\u0000\u0000�\u0002\u0000\u0000\r\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000�\u0006\u0000\u0000xl/styles.xmlPK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,U\u0007$�h�\u0000\u0000\u0000b\u0001\u0000\u0000\u000f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0010\b\u0000\u0000xl/workbook.xmlPK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,U�\u0003;��\u0000\u0000\u00003\u0002\u0000\u0000\u001a\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00006\t\u0000\u0000xl/_rels/workbook.xml.relsPK\u0001\u0002\u0014\u0000\u0014\u0000\b\b\b\u0000�,U�Q�.�\u0001\u0000\u0000}\u0005\u0000\u0000\u0018\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000R\n\u0000\u0000xl/worksheets/sheet1.xmlPK\u0005\u0006\u0000\u0000\u0000\u0000\t\u0000\t\u0000?\u0002\u0000\u0000�\f\u0000\u0000\u0000\u0000"
}
What can I be doing wrong?
ok guys,
I found the answer to my question. The thing is, I was using Redux for the request. For some reason it didn't load my file.
Now I switched to fetch request and everything started working
const requestReport = (selectedObj) => fetch(`/api/reports/download-report`, {
headers: new Headers({'content-type': 'application/json'}),
method: "POST",
body: JSON.stringify(selectedObj),
}).

Unable to access lambda function from s3 hosted react application

I have a react application which has a post method requesting data from a lambda function. It is working fine in my local machine. But when I deployed the application on s3 bucket and try to access it through s3 endpoint, it gives me 405 Method not allowed error. After search I found that post method will not work with s3 endpoint, So again I tried with rest api endpoint, but it shows file name xml. I am able to access application when I add index page to rest api endpoint, But here also I am getting 412 error in post method. So in any case I am getting error with post method. Can someone help me with that. I don't know what I am missing. Below is my code:
const apiUrl = process.env.NODE_ENV === 'production' ? process.env.REACT_APP_PROD_API_URL : process.env.REACT_APP_DEV_API_URL;
const params = (query: string) => ({
headers: {
"Accept": "application/sparql-results+json",
"Accept-Encoding": "gzip",
"Content-Type": "application/x-www-form-urlencoded"
},
method: "POST",
body: `query=${encodeURIComponent(query)}`
})
const fetchClinicalStudies = async () => {
const clinicalStudiesQuery = `
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX disco: <http://rdf-vocabulary.ddialliance.org/discovery#>
SELECT ?id ?label
WHERE {
?id a disco:Study ;
dcterms:title ?label .
}
ORDER BY ?label`
const response = await fetch(apiUrl ?? '', params(clinicalStudiesQuery))
return response;
}

NEXTJS Sending formdata to api endpoint - no response

Hellooooo.
I am trying to upload images to AWS S3 and I've stumbled upon something that irritates me quite a lot. I simply can't seem to understand why it would act like this.
Aight so.. I am using formdata to send data to my api endpoint. The API gets called without any issues, no errors, nothing.. Like srly, nothing. Before getting to image upload I was just using a basic body post request with fetch but now I am using formdata in order to upload images.
Here's my "fetch/axios/sendthingy"
const formData = new FormData();
formData.append('file', validFiles[0]);
formData.append('postTitle', postTitle);
formData.append('postProduct', postProduct);
formData.append('postDescription', postDescription);
formData.append('postPrice', postPrice);
formData.append('postCoins', cryptocoins);
formData.append('postCategory', Cate);
formData.append('postSubCategory', subCat);
formData.append('postCryptoDiscount', cryptoDiscount);
for (const entry of formData.entries())
{
// debugging
console.log(entry)
}
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json"); // or form data?
const requestOptions = {
method: 'POST',
body: formData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, // or use myHeaders
// body: JSON.stringify({ postTitle: postTitle, postProduct: postProduct, postDescription: postDescription, postPrice: postPrice, postCoins: cryptocoins, postImage: validFiles[0], postCategory: Cate, postSubCategory: subCat, postCryptoDiscount: cryptoDiscount})
};
// const res = await fetch(`http://localhost:3000/api/posts/create`, requestOptions)
// const data = await res.json();
axios.post('http://localhost:3000/api/posts/create', formData).then(console.log).catch(console.error)
My comments are from before using formdata. That worked.
As you can see, I have installed Axios. I wanted to see if axios would fix the return stuff. No luck. Any ideas on how to solve this or how to send images to api with or without formdata?
Right, let's move on. Next issue is that I can see it calling my API endpoint but my api endpoint does nothing. It returns nothing. Does nothing. But when I call the api from postman it works just as it should. How come? It creates the post and returns the correct data. Any ideas?
My API:
export default async function handler(req, res) {
console.log('body', req.body);
/*
Redacted
*/
res.status(200).json({response: 'test', code: 200, message:"Successfully created the post."})
})
Why is my API not doing anything locally... It will only work with formdata if I call the endpoint from postman.
This is what I mean.. The dark one is Firefox, white is Postman:
Firstly , you should use multipart/form-data for uploading files. And to see the response you have to console.log the response like this:
axios.post(
'http://localhost:3000/api/posts/create',
formData,
{headers:{'Content-Type':'multipart/form-data'}}
).then(res => console.log(res))
.catch(err => console.error(err))

InvalidSignatureException from POST request

I have a Lambda function that handles reading data from a file(stored inside S3 bucket) as well as inserting data to a Dynamodb table. This Lambda function is exposed as a REST endpoint using API gateway. The function accepts GET request as well as POST request. I'm making GET/POST requests from my REACT project using axios and aws4(for signing) libraries. GET request is to read data from a file stored inside S3 and it works just fine. And POST request is for inserting data into Dynamodb table. However, it doesn't work and AWS returns InvalidSignatureException error as a respond. This is an excerpt of my code :
createAWSSignedRequest(postData) {
let request = {};
if (postData) {
request = {
host: process.env.AWS_HOST,
method: 'POST',
url: process.env.AWS_URL,
path: process.env.AWS_PATH,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
}
} else {
request = {
host: process.env.AWS_HOST,
method: 'GET',
url: process.env.AWS_URL,
path: process.env.AWS_PATH
}
}
let signedRequest = aws4.sign(request, {
secretAccessKey: process.env.AWS_SECRET_KEY,
accessKeyId: process.env.AWS_ACCESS_KEY
});
return signedRequest;
}
This is how GET request is made :
let signedRequest = this.createAWSSignedRequest('GET');
axios(signedRequest)
.then(response => {
})
.catch((error) => {
console.log("error",error);
});
This is how POST request is made :
const data = {
uuid: "916b7d90-0137-11e8-94e6-116965754e23", //just a mock value
date : "22/jan/2018",
user_response: [
{
question:"this is quesiton1",
choice:"user selected A"
},
{
question:"this is quesiton2",
choice: "user selected b"
},
{
question:"this is quesiton3",
choice: "user selected C"
}
]
};
let signedRequest = this.createAWSSignedRequest(data);
axios(signedRequest)
.then(response => {
......
})
.catch((error) => {
console.log("error",error);
});
As you can see, the code for both GET and POST requests are exactly the same (except payload and method type). I'm singing with the same secret access key and access key id for both requests. I'm not sure why one request results in "InvalidSignatureException" when the other doesn't. Can anyone shed a light on this issue for me.
Thanks
After having discussion with AWS4 lib developer, I figured out what I did wrong. AWS4 uses "body" as a payload attribute to compute signature. However, Axios uses "data" attribute as payload. My mistake was only setting either one of them. So when I set just "data" attribute, the payload was present in the request and content-length is computed correctly. However, the signature was incorrect since the payload was not taken into consideration when computing signature. When I set just "body", payload was not present in the request because Axios does not use "body" attribute for payload. The solution is to set both attributes with payload. I hope this helps to anyone who are having the same issue I have.
If you use the AWS Amplify library it has a module called API which should fit your use cases, and it will perform Sigv4 signing for you either with authenticated or unauthenticated roles. The Auth category uses Cognito as the default implementation. For instance:
npm install aws-amplify --save
Then import and configure the lib:
import Amplify, { API } from 'aws-amplify';
Amplify.configure({
Auth: {
identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab',
region: 'XX-XXXX-X'
},
API: {
endpoints: [
{
name: "APIName",
endpoint: "https://invokeURI.amazonaws.com"
}
]
}
});
Then for your API Gateway endpoint calling a Lambda:
let apiName = 'MyApiName';
let path = '/path';
let options = {
headers: {...} // OPTIONAL
}
API.get(apiName, path, options).then(response => {
// Add your code here
});
More info here: https://github.com/aws/aws-amplify

Multiple file uploads to Cloudinary with Axios in React

I have tried implementing the superagent way of uploading multiple files in axios. But somehow, I'm getting an error in console
Failed to load https://api.cloudinary.com/v1_1/xxxx/image/upload:
Request header field Authorization is not allowed by
Access-Control-Allow-Headers in preflight response.
My upload handler looks like this
uploadFile(){
const uploaders = this.state.filesToBeSent.map(file => {
const formData = new FormData();
formData.append("file", file);
formData.append("upload_preset", "xxxxx");
formData.append("api_key", "xxxxx");
formData.append("timestamp", (Date.now() / 1000) | 0);
return axios.post(url, formData, {
headers: { "X-Requested-With": "XMLHttpRequest" },
}).then(response => {
const data = response.data;
const fileURL = data.secure_url
console.log(data);
})
});
// Once all the files are uploaded
axios.all(uploaders).then(() => {
// ... perform after upload is successful operation
console.log("upload completed ", uploaders);
});
}
I have got this example from here
Another thing is confusing to me. In superagent we can attach parameters to the request field which includes API Secret Key of Cloudinary like this:
const paramsStr = 'timestamp='+timestamp+'&upload_preset='+uploadPreset+secretKey;
const signature = sha1(paramsStr);
const params = {
'api_key': 'xxxx',
'timestamp': timestamp,
'upload_preset': uploadPreset,
'signature': signature
}
Object.keys(params).forEach(key => {
uploadRequest.field(key, params[key])
});
But in that example, it is not mentioned how to append the secret key and other params to axios.
You will need to generate the signature on your backend, and then perform the upload with the generated signature.
You can generate a signature via the following instructions- https://support.cloudinary.com/hc/en-us/articles/203817991-How-to-generate-a-Cloudinary-signature-on-my-own-
You can also take a look at the following example on how to append the signature to your request. It's in PHP, however, the guidelines still apply.
https://gist.github.com/taragano/a000965b1514befbaa03a24e32efdfe5

Resources