JavaScript CORS error when uploading files with Axios - reactjs

I am developing a web application with Flask on the backend and React and Redux on the frontend.
I want to add a "Change Profile Picture" option to the profile page but whenever I make a post request with axios to my /api/user/upload_image/ route, i get the following errors:
Access to XMLHttpRequest at 'http://localhost:5000/api/user/update_image' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
PATCH http://localhost:5000/api/user/update_image net::ERR_FAILED
Which is weird becuase I have set up my CORS wrapper in my Flask app like so:
self.cors = CORS(self.app, resources={r"/api/*": {"origins": "*"}})
which should allow requests to /api/ from all origins.
I also tried to do the same thing with Postman and it worked like a charm - uploaded the file and saved it to /server/public/profile_pictures/
When i try to upload regular JSON text from my react application it works as well. It bugs out on file uploads only.
Here is the JSX for the input + the event handler
<label>
Change Profile Picture
<input onChange={(e) => {
this.setState({image: e.target.files[0]})}
} type="file" name="image" />
</label>
Then i have a submit button which dispatches the following action with this.state.image as a parameter:
export const updateImage = (file) => {
return async (dispatch, getState) => {
const formData = {
user_id: getState().currentUser.user.user_id,
auth_key: getState().currentUser.auth_key,
image: file
}
Axios.patch("http://localhost:5000/api/user/update_image", formData, {
headers: {
'Content-Type' : 'multipart/form-data'
}
})
.then(response => {
dispatch({type: UPDATE_IMAGE, payload: response.data})
})
}
I tried using the built in formData method to create the JS object too but that was no good either.
Finally here is the python method which is called when the /api/user/update_image route is hit:
def update_image(self, request):
image = request.files['image']
data = request.params
image.save("./public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg")
fsql.update("""UPDATE users SET profile_picture = %s WHERE user_id = %s""", ("/public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg", data['user_id']))
return jsonify({
"error_code" : "200",
"error_message" : "Success"
})

I actually solved this about a week and a half ago but I checked the status today.
So the solution was to make a few changes to my config parameter and CORS parameters. Here is the configs i am using right now:
config = {
'ORIGINS': [
'http://localhost:3000', # React
'http://127.0.0.1:3000', # React
],
'SECRET_KEY': '...' #secret key
self.cors = CORS(self.app, resources={
r'/api/*': {
"Access-Control-Allow-Origin": config["ORIGINS"],
"Access-Control-Allow-Credentials": True,
'supports_credentials': True
},
},
supports_credentials = True,
expose_headers = "*"
)
self.app.config['UPLOAD_FOLDER'] = r'/*' # Change this to only the folder you want to save images to
self.app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Change this according to your file size
This solved my CORS and file transport issues.
I really hope this helps someone. The CORS docs on flask-cors do not cover everything in regards to file uploading and session storage so we kind of have to solve the errors without knowing how everything works - like trying to solve a puzzle with missing pieces.
HMU in messages if you have any good tools for CORS in flask which are well documented and have a community around them.

Related

Expose Content-Disposition header on NextJS

I'm trying to get the Content-Disposition header in a response from an external API using axios.
Despite the header being present in Chrome DevTools Network Response, I can't seem to have access to that specific header from server.
I found this article talking about exposing the Content-Disposition header through Access-Control-Expose-Headers but I'm not quite sure how to implement it in Nextjs.
I tried editing the next.config.js file like below, by following directions from this Nextjs Documentation page, regarding security headers, but had no luck
/** #type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
async headers() {
// to allow specific headers to appear in requests
// https://nextjs.org/docs/advanced-features/security-headers
const securityHeaders = [
// important
{ key: "Access-Control-Expose-Headers", value: "Content-Disposition" },
]
return [
{
source: '/:path*', // req path
headers: securityHeaders
}
]
}
}
This is the API call I made using axios:
// lib/utils.js
export async function downloadFile(collectionName: string, documentId: string) {
const res = await axios.get(
`https://api.myapiendpoint.com/file/${collectionName}/${documentId}`
);
console.log(res.headers);
}
Chrome DevTools log:
console.log output:
// these are the only headers I receive
{
"content-length": "195687",
"content-type": "text/csv; charset=utf-8",
"last-modified": "Thu, 10 Feb 2022 16:00:05 GMT"
}
I am 99.99% sure and I believe this is most probably backend issue. I had the exact same problem when i needed to implement download pdf/csv logic (with file name) which required content-disposition header to be accessed from response.
Now, no matter what I tried I always got to see that header in dev tools and also in postman but not in my console. After lots of efforts and convincing my backend team member, it turned out that backend didn't expose it.
Check your backend (whatever technology it uses), the problem lies there ;)

Blocked by CORS policy "...does not have HTTP ok status" (Amplify and ReactJS, AWS Gateway and Lambda)

I'm almost embarassed to be asking this question due to CORS support out there on SO but I can't get by:
Access to XMLHttpRequest at 'https://a93xxxxx.execute-api.eu-west-1.amazonaws.com/dev[object%20Object]' from origin 'https://www.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
I've even published my React project with Amplify and attempted it from the real domain name to even eliminate anything to do with the development environment (Cloud 9 running npm version 6.14.8)
I've also made a test running Chrome with the --disable-web-security flag.
My Lambda function contains the following (out of the box stub)
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
// Uncomment below to enable CORS requests
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers" : "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With",
"Access-Control-Allow-Methods" : "OPTIONS,POST,GET,PUT"
}
,
body: JSON.stringify("Hello from Lambda!")
};
return response;
};
Note that I've uncommented the CORS request part and the response statusCode is set to 200.
The code in my application that execute when a submission form is sent from the client:
uploadcontactusdata = async data => {
try {
console.log("Contact Us pressed")
const settings = {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
const fetchResponse = await API.post('econtactus', settings);
Notification({
title: 'Success',
message: 'Notification has been sent',
type: 'success'
});
}
catch (err) {
console.log("unable to send");
console.error(err)
}
}
I created the API Gateway + Lambda using Amplify (version 4.41.2). Not sure where else to look now. Any clues will be appreciated. Thanks
You can completely get past the need for api gateway by using appsync.
amplify add api
Choose graphql (I have not tried using rest but you shouldn't need it) choose the basic schema, edit it if you'd like, and publish. Once it's published you can create your own method. You can view this inside the AppSync UI under Schema.
type Mutation {
yourMethod(input: Input!): TableName <-- add your method to the list
}
Now inside Appsync choose Data Sources and add datasource. Give it a name, choose lambda as the type, then find your lambda in the list. Once it's added go back to your schema and find the method you created above. On the right side bar locate your method and click the attach link. Find the data source you just added. Fill out the region and lambda ARN. MAKE SURE you choose new role and not an existing one.
You might need to configure the request and response templates.
For request:
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": $util.toJson($context.args)
}
For response:
$util.toJson($context.result)
Now you can call your lambda directly from the UI and return your result without worrying about CORS or managing API Gateway.

React/Axios API Get Request Issues (CORS & Internal Server Error 500)

I am attempting to complete an axios GET request to an API and I'm running into an Internal Server Error - 500 and I'm curious if this is simply my code and/or my attempt at making this call or something else. Even though the CORS issue seems to be behind me, I'll start from the beginning just in case its related to my current issue.
My initial attempt at the request gave me the following CORS error:
...from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight
request doesn't pass access control check: It does not have HTTP ok status.
After doing a lot of research on this, I found that I could append https://cors-anywhere.herokuapp.com to my target API URL and get around this issue. So far, so good but now I am getting the following locally in my browser: net::ERR_NAME_NOT_RESOLVED
So I decided to jump over to Postman and input the given headers to access this API to see if I could find more information and I'm getting the following on Postman:
{
"timestamp": "2020-11-13T01:04:47.288+0000",
"message": "General error occurred please contact support for more details",
"error": "Internal Server Error",
"status": 500
}
Now, within the documentation of this API, it states that a 500 is a server error on their part but I'm not confident in that as I think it may just be my own doing here. So I basically have two questions...
Should the developer of the API do/change anything to avoid the CORS issue or is that a common thing to run into?
Is the 500 error response on me or them?
Below is my axios request in my App.js file of my React application. Please let me know if any other code or info is needed. Thanks so much in advance for any help!
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
class App extends Component {
state = {
events: []
}
constructor() {
super();
const proxyURL = 'https://cors-anywhere.herokuapp.com'
const URL = 'https://api.example.com/api/'
const proxiedURL = proxyURL + URL
axios.get(proxiedURL, {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + process.env.REACT_APP_AUTH_API_KEY
}
})
.then((res) => {
console.log(res.data)
})
.catch((error) => {
console.log(error)
})
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>Data</h1>
</header>
</div>
);
}
}
export default App;
According to the documentation for cors-anywhere:
This API enables cross-origin requests to anywhere.
Usage:
/ Shows help /iscorsneeded This is the only resource
on this host which is served without CORS headers. /
Create a request to , and includes CORS headers in the response.
Your code is missing a trailing slash after https://cors-anywhere.herokuapp.com to work, i.e.: https://cors-anywhere.herokuapp.com/.
To answer your two other questions:
CORS issues are very common and it is up to the developer of the API to set from which domain the API can be called. More on MSDN
The 500 response means this in an internal server error, so on the server-side. Though it can be because of many reasons, like querying the wrong URL, passing unexpected data... Ideally all these should be covered and different errors would be returned every time but this is rarely the case. :)

undefined in file path when trying to upload a file in React Application using ReactS3Uploader and SignedUrl

I am a newbie into React. I have been trying to upload file (images, json files etc) to AWS S3 bucket from a reactJS application using ReactS3Uploader (version 4.8.0). I am following this example : https://www.npmjs.com/package/react-s3-uploader
I have added the below code into one of my component where I want the file upload functionality :
<ReactS3Uploader
getSignedUrl={getSignedUrl}
accept="image/*"
s3path="/uploads/test/"
preprocess={this.onUploadStart}
onSignedUrl={this.onSignedUrl}
onProgress={this.onUploadProgress}
onError={this.onUploadError}
onFinish={this.onUploadFinish}
signingUrlHeaders={{ }}
signingUrlQueryParams={{ }}
signingUrlWithCredentials={ true } // in case when need to pass authentication credentials via CORS
uploadRequestHeaders={{ 'x-amz-acl': 'public-read' }} // this is the default
contentDisposition="auto"
scrubFilename={(filename) => filename.replace(/[^\w\d_\-.]+/ig, '')}
inputRef={cmp => this.uploadInput = cmp}
autoUpload={true}
server="http://cross-origin-server.com"
/>
I have also created another component for getSignedUrl (S3SignedUrl.js) as follows (as described here https://www.npmjs.com/package/react-s3-uploader ) :
import React, { Component } from 'react';
import { toast } from 'react-toastify';
import axios from '../../shared/axios';
function getSignedUrl(file, callback) {
console.log('.........Inside getSignedUrl()>>file.nameeeee.........'+file.name)
console.log('.........Inside getSignedUrl()>>file.size.........'+file.size)
const filename = file.name;
const params = {
filename: file.name
//contentType: file.type
};
var headers = {
'Content-Type': 'application/json'
}
axios.post(`/api/link/admin/v1/s3/sign?filename=${filename}`, {headers: headers})
.then(data => {
console.log('data.data.signedUrl>>>>>>>>>>>'+data.data.signedUrl)
callback(data);
return data.data
})
.catch(error => {
console.error(error);
});
}
export default getSignedUrl;
I have a groovy based backend api (springboot application) which creates the s3 signed url in the following format :
{
"signedUrl": “<complete signed url>”,
"uploadPath": “mybucket/apidocs/dev/version/logo/04137a9c-fb60-48dd-ae0f-c53d78e4e379/logo.png",
"expiresAt": 1552083549794
}
I am successfully able to call my groovy /s3/sign url from my react application through (S3SignedUrl.js which uses Axios) but right after that when ReactS3Uploader component tries to upload the file to the AWS S3 bucket, it gives me an error with HTTP 403.
When I see into the network tab (by inspecting within the google chrome), the underlying call being made my ReactS3Uploader component is
PUT https://localhost:3000/apps/gateway/undefined with Http 403
I am not sure what is undefined here within the url. Shouldn’t ReactS3Uploader component automatically be doing a HTTP PUT to the signedURL ?
I do see some fixes in react-s3-uploader version 4.6.2 around undefined in file path when not providing s3path property. https://changelogs.md/github/odysseyscience/react-s3-uploader/
But not sure if it has anything too do with the problem I am getting. By the way I am using using version 4.8.0.
Just to confirm I can successfully upload the file using that SignedURL manually thru curl.
Any help here would highly be appreciated.
Thanks in advance
I know this is an old post, but I've been searching for something similar and came across this.
You should have callback(data.data);.
ReactS3Uploader will redirect to an undefined URL if it's configured to use a getSignedUrl function and is not returned a signedUrl field.

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

Resources