My website has a separate server for the front-end and back-end, and so my back-end server needs to open up CORS permissions so that the front-end can request data from it.
I am using Flask-Cors successfully in development, but it doesn't work when I deploy to production. (please note that I have looked at other flask-cors questions on SO, but none of them fit my situation)
Here is the relevant code that is working in development:
# 3rd party imports
import flask
from flask import Flask, request, redirect, send_from_directory, jsonify
from flask_cors import CORS
# Create the app
app = Flask(__name__)
CORS(app, origins=[
'http://localhost:5001',
])
# Define the routes
@app.route('/')
def index():
# no CORS code was necessary here
app.logger.info(f'request is: {flask.request}')
What I've tried:
'http://162.243.168.182:5001'
to the CORS list is not enough to resolve the issue, although I understand it should be there.'*'
to allow ALL origins does not work either. (very suspicious!)Please note that I am using a Docker container, so my environment between development and prod are almost identical. But what's different is that I'm on a different server and I've modified the front-end to send the request to the new IP address (resulting in the famous “Access-Control-Allow-Origin” header missing
CORS error).
Now I'm wondering if the flask.request
object is somehow missing information, and this causes Flask-Cors to not send the Access-Control-Allow-Origin header like it's supposed to. I can provide that logging info if you think it would help!
The Dockerfile I am using in PROD is:
# base image
FROM tiangolo/uwsgi-nginx-flask:python3.8-2020-12-19
# install deps
RUN pip3 install ediblepickle==1.1.3
# RUN pip3 install flask==1.1.2 # pre-installed on tiangolo/uwsgi-nginx-flask
RUN pip3 install flask-cors==3.0.9
RUN pip3 install numpy==1.19.2
RUN pip3 install scipy==1.5.2
RUN pip3 install pandas==1.1.2
RUN pip3 install networkx==2.5
# pull in files for deployment
COPY ./app /app
# Note that there is no CMD to run because the CMD set in the base image is what we already wanted. As long as the Flask app is called `app`, the python file is named `main.py`, the parent directory is named `app`, and that same directory gets copied into `/app`, then the base image is designed to make our app work out-of-the-box.
and the command I use to kick it off is:
docker build -t mvlancellotti/tennis-backend:prod -f prod.Dockerfile . && docker run --rm -p 5000:80 --name tennis-backend-container mvlancellotti/tennis-backend:prod
Going into the /app
directory of the container, there is the file uwsgi.ini
with contents:
[uwsgi]
module = main
callable = app
which seems to work, and the file /etc/nginx/nginx.conf
has contents:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
daemon off;
and the file /etc/nginx/conf.d/nginx.conf
has contents:
server {
listen 80;
location / {
try_files $uri @app;
}
location @app {
include uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
}
location /static {
alias /app/static;
}
}
Simply add @cross_origin() below a call to Flask's @app. route(..) to allow CORS on a given route.
To initiate a cross-origin request, a browser sends the request with an Origin: <domain> HTTP header, where <domain> is the domain that served the page. In response, the server sends Access-Control-Allow-Origin: <domain> , where <domain> is either a list of specific domains or a wildcard to allow all domains.
The thing is, it is the server you are requesting resources from should have the CORS header (let it be serverA
), not the server which requires remote files (serverB
). If serverA
is NGINX
or Apache2
you can use this to add CORS header to responses:
NGINX:
add_header Access-Control-Allow-Origin *;
# or
add_header Access-Control-Allow-Origin serverB;
Apache2:
Header set Access-Control-Allow-Origin "*"
# or
Header set Access-Control-Allow-Origin "serverB"
This above means "allow * or serverB to fetch resources from this server (serverA)".
For anyone new to CORS I recommend these two (1, 2) short readings from Mozilla. I think they are great at explaining the basics.
Kindly, You need to consider several things in your production environment. First of all, please share your docker-related configurations ( Dockerfile, Docker-Compose if you have ) because you are trying to fix the issue somewhere that is not related to the root cause. for example, if you are using Apache or Nginx to serve HTTP requests as reverse_proxy, so you need to add related headers into their configuration. But I suggest you use the following codes, previously I faced some issue like you, and the following code resolved mine :
cors = CORS(flask_app, resources={r"/api/*": {"origins": "*", "allow_headers": "*", "expose_headers": "*"}})
Update: adding Nginx config
Here is the Most Complete & Wide-Open CORS configuration for Nginx:
Nginx Config Complete CORS Open
For some of the configurations, Wildcard ( * sign ) is not supported by some old browsers, So, I've added a list of somehow all possible values, but you need to modify it based on your application requirements and policies. Also, you can move the "add_headers" part to any location of your config file that needs to be there.
server {
listen 80;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'CONNECT, DEBUG, DELETE, DONE, GET, HEAD, HTTP, HTTP/0.9, HTTP/1.0, HTTP/1.1, HTTP/2, OPTIONS, ORIGIN, ORIGINS, PATCH, POST, PUT, QUIC, REST, SESSION, SHOULD, SPDY, TRACE, TRACK';
add_header 'Access-Control-Allow-Headers' 'Accept, Accept-CH, Accept-Charset, Accept-Datetime, Accept-Encoding, Accept-Ext, Accept-Features, Accept-Language, Accept-Params, Accept-Ranges, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin, Access-Control-Expose-Headers, Access-Control-Max-Age, Access-Control-Request-Headers, Access-Control-Request-Method, Age, Allow, Alternates, Authentication-Info, Authorization, C-Ext, C-Man, C-Opt, C-PEP, C-PEP-Info, CONNECT, Cache-Control, Compliance, Connection, Content-Base, Content-Disposition, Content-Encoding, Content-ID, Content-Language, Content-Length, Content-Location, Content-MD5, Content-Range, Content-Script-Type, Content-Security-Policy, Content-Style-Type, Content-Transfer-Encoding, Content-Type, Content-Version, Cookie, Cost, DAV, DELETE, DNT, DPR, Date, Default-Style, Delta-Base, Depth, Derived-From, Destination, Differential-ID, Digest, ETag, Expect, Expires, Ext, From, GET, GetProfile, HEAD, HTTP-date, Host, IM, If, If-Match, If-Modified-Since, If-None-Match, If-Range, If-Unmodified-Since, Keep-Alive, Label, Last-Event-ID, Last-Modified, Link, Location, Lock-Token, MIME-Version, Man, Max-Forwards, Media-Range, Message-ID, Meter, Negotiate, Non-Compliance, OPTION, OPTIONS, OWS, Opt, Optional, Ordering-Type, Origin, Overwrite, P3P, PEP, PICS-Label, POST, PUT, Pep-Info, Permanent, Position, Pragma, ProfileObject, Protocol, Protocol-Query, Protocol-Request, Proxy-Authenticate, Proxy-Authentication-Info, Proxy-Authorization, Proxy-Features, Proxy-Instruction, Public, RWS, Range, Referer, Refresh, Resolution-Hint, Resolver-Location, Retry-After, Safe, Sec-Websocket-Extensions, Sec-Websocket-Key, Sec-Websocket-Origin, Sec-Websocket-Protocol, Sec-Websocket-Version, Security-Scheme, Server, Set-Cookie, Set-Cookie2, SetProfile, SoapAction, Status, Status-URI, Strict-Transport-Security, SubOK, Subst, Surrogate-Capability, Surrogate-Control, TCN, TE, TRACE, Timeout, Title, Trailer, Transfer-Encoding, UA-Color, UA-Media, UA-Pixels, UA-Resolution, UA-Windowpixels, URI, Upgrade, User-Agent, Variant-Vary, Vary, Version, Via, Viewport-Width, WWW-Authenticate, Want-Digest, Warning, Width, X-Content-Duration, X-Content-Security-Policy, X-Content-Type-Options, X-CustomHeader, X-DNSPrefetch-Control, X-Forwarded-For, X-Forwarded-Port, X-Forwarded-Proto, X-Frame-Options, X-Modified, X-OTHER, X-PING, X-PINGOTHER, X-Powered-By, X-Requested-With';
add_header 'Access-Control-Expose-Headers' 'Accept, Accept-CH, Accept-Charset, Accept-Datetime, Accept-Encoding, Accept-Ext, Accept-Features, Accept-Language, Accept-Params, Accept-Ranges, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin, Access-Control-Expose-Headers, Access-Control-Max-Age, Access-Control-Request-Headers, Access-Control-Request-Method, Age, Allow, Alternates, Authentication-Info, Authorization, C-Ext, C-Man, C-Opt, C-PEP, C-PEP-Info, CONNECT, Cache-Control, Compliance, Connection, Content-Base, Content-Disposition, Content-Encoding, Content-ID, Content-Language, Content-Length, Content-Location, Content-MD5, Content-Range, Content-Script-Type, Content-Security-Policy, Content-Style-Type, Content-Transfer-Encoding, Content-Type, Content-Version, Cookie, Cost, DAV, DELETE, DNT, DPR, Date, Default-Style, Delta-Base, Depth, Derived-From, Destination, Differential-ID, Digest, ETag, Expect, Expires, Ext, From, GET, GetProfile, HEAD, HTTP-date, Host, IM, If, If-Match, If-Modified-Since, If-None-Match, If-Range, If-Unmodified-Since, Keep-Alive, Label, Last-Event-ID, Last-Modified, Link, Location, Lock-Token, MIME-Version, Man, Max-Forwards, Media-Range, Message-ID, Meter, Negotiate, Non-Compliance, OPTION, OPTIONS, OWS, Opt, Optional, Ordering-Type, Origin, Overwrite, P3P, PEP, PICS-Label, POST, PUT, Pep-Info, Permanent, Position, Pragma, ProfileObject, Protocol, Protocol-Query, Protocol-Request, Proxy-Authenticate, Proxy-Authentication-Info, Proxy-Authorization, Proxy-Features, Proxy-Instruction, Public, RWS, Range, Referer, Refresh, Resolution-Hint, Resolver-Location, Retry-After, Safe, Sec-Websocket-Extensions, Sec-Websocket-Key, Sec-Websocket-Origin, Sec-Websocket-Protocol, Sec-Websocket-Version, Security-Scheme, Server, Set-Cookie, Set-Cookie2, SetProfile, SoapAction, Status, Status-URI, Strict-Transport-Security, SubOK, Subst, Surrogate-Capability, Surrogate-Control, TCN, TE, TRACE, Timeout, Title, Trailer, Transfer-Encoding, UA-Color, UA-Media, UA-Pixels, UA-Resolution, UA-Windowpixels, URI, Upgrade, User-Agent, Variant-Vary, Vary, Version, Via, Viewport-Width, WWW-Authenticate, Want-Digest, Warning, Width, X-Content-Duration, X-Content-Security-Policy, X-Content-Type-Options, X-CustomHeader, X-DNSPrefetch-Control, X-Forwarded-For, X-Forwarded-Port, X-Forwarded-Proto, X-Frame-Options, X-Modified, X-OTHER, X-PING, X-PINGOTHER, X-Powered-By, X-Requested-With';
location / {
try_files $uri @app;
}
location @app {
include uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
}
location /static {
alias /app/static;
}
}
Nginx Simpler CORS Config File
Also, you can use the following code, but maybe you face some cors errors that you can solve quickly by comparing with the previous sample. most of the time there is some header in the response which is not allowed and exposed in the lists.
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, HEAD';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'Origin,Content-Type,Accept,Authorization,Access-Control-Expose-Headers,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Expose-Headers';
add_header 'Access-Control-Expose-Headers' 'Origin,Content-Type,Accept,Authorization,Access-Control-Expose-Headers,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Expose-Headers'
You can also check out this link for more detail on CORS. Link
I would prefer to add a comment, but still not enough rep..
Nginx add_header won't work for error codes.
Also when you receive any kind of error (400, 500, 502, etc) the header will be missing. And the browser will show you CORS, but nevermind, something went wrong elsewhere. It's common to lose a lot of time because of this...
I accessed your app (sorry if it's not mentioned to do so). It loads and some filter options results in 502 and browser will say: Oh, CORS! But looks like something is dying and coming back.
Info regarding add_header and errors: https://serverfault.com/questions/431274/nginx-services-fails-for-cross-domain-requests-if-the-service-returns-error
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With