Dealing with CORS without flask-cors— An example of React and Flask
Introduction
CORS is the abbreviation of Cross-Origin-Resource-Sharing. It is a mechanism that protects the users from being attacked by such as Cross-Site-Scripting (XSS). When you are asking for resources of a server from a different domain, you need to be in the whitelist of the server. If you are developing the frontend with Chrome and want to fetch
data from a server with different domain, you might receive the CORS error. For example, one of my project used React as frontend and Flask as backend. When I wanted to fetch data from the Flask server, I received this error:
What happens behind the scene?
You see the above error message because whenever you want to send a HTTP request via a browser, say Chrome, it will first send a preflight request to the server before sending yours. For example, if you want to send a POST request to your server, you should see two requests records in your server’s logs with different methods: one is OPTIONS and the other is the POST. The former is the preflight request sent from the browser and the latter is the actual request you want to send. The preflight request is to ensure that you have the access to ask for resources from the server.
How should I solve it
The server should add the following headers to the response: Access-Control-Allow-Origin, Access-Control-Allow-Headers and Access-Control-Allow-Methods. These headers tell the browser that you are trusted by the server owner so you can request resources from your domain. Notice that this is just the response for the preflight request. For the actual POST requests you make, the server still needs to add Access-Control-Allow-Origin header to the response. Most people would recommend using a package called flask-cors
. What it does is acting as a middleware that adds some headers to your response so the browser know you are trusted by the server and thus you can ask for resources. However, it doesn’t work for me somehow. I didn’t look into its implementations so I didn’t know the reason. If you are facing the same problem like me, you can manually add these headers to the response with just two functions:
def build_preflight_response():
response = make_response()
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add('Access-Control-Allow-Headers', "*")
response.headers.add('Access-Control-Allow-Methods', "*")
return responsedef build_actual_response(response):
response.headers.add("Access-Control-Allow-Origin", "*")
return response
In case you want to see the complete source code for the server, I have put a working Flask server:
import requestsfrom flask import Flask, render_template, request, jsonify, make_responseapp = Flask(__name__)@app.route('/', methods=['OPTIONS','POST'])
def greeting():
if request.method == 'OPTIONS':
return build_preflight_response()
elif request.method == 'POST':
req = request.get_json()
# query user with req['id']
# for demonstration, we assume the username to be Eric
return build_actual_response(jsonify({ 'name': 'Eric' }))def build_preflight_response():
response = make_response()
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add('Access-Control-Allow-Headers', "*")
response.headers.add('Access-Control-Allow-Methods', "*")
return responsedef build_actual_response(response):
response.headers.add("Access-Control-Allow-Origin", "*")
return responseif __name__ == '__main__':
app.run(host='0.0.0.0')
The Flask app is run on port 5000 in default. Now you can fetch
the data from your server:
import React, {useState, useEffect} from "react";
import ReactDOM from "react-dom";import "./styles.css";function App() {
const [username, setUsername] = useState('User');
useEffect(() => {
fetch('http://localhost:5000', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id:'1234' })
})
.then(res => res.json())
.then(data => { setUsername(data.name) });
})
return (
<div className="App">
<h1>{ username }</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Now you should see the username shown on the screen by accessing http://localhost:3000. Congratulations!