7

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:

  • Adding my server's ip address 'http://162.243.168.182:5001' to the CORS list is not enough to resolve the issue, although I understand it should be there.
  • It seems that using '*' 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!

More information!

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;
    }
}
mareoraft
  • 2,402
  • 2
  • 19
  • 50

3 Answers3

4

If you are using NGINX or Apache2 as a proxy before the actual application, you can configure it too handle CORS header for you.

NGINX:

add_header Access-Control-Allow-Origin *;

Apache2:

Header set Access-Control-Allow-Origin "*"

Place it where initial response is generated and it will allow loading resources from any origin. In prod you should probably replace * with the origin you are going to use. If the origin is not going to change often or dynamically - you probably don't need flask CORS.

Otherwise first thing you need is to open a developer console in your browser and check response headers of your initial request to the website. This is where origin and CORS policy is defined. There should be a response header Access-Control-Allow-Origin which defines from where the browser is allowed to load additional resources. If there is no header then no extra origins are allowed. If the header is there and you still got the error, then the resource origin you are trying to load do not match the rule. Check for a typo in this case.

For anyone new to CORS I recommend these two (1, 2) short readings from Mozilla. I think they are great in explaining the basics.

anemyte
  • 8,184
  • 1
  • 4
  • 21
  • I have added `add_header Access-Control-Allow-Origin *;` to my `/etc/nginx/conf.d/nginx.conf` file, but the same CORS error persists: _Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/?gender=m&stat=ace&normalization=percent&reverse=false&limit=5. (Reason: CORS request did not succeed)._ – mareoraft Jan 15 '21 at 18:13
  • Please also take a look at the extra info I have included above. – mareoraft Jan 15 '21 at 18:24
  • @mareoraft Where have you put that line? Judging by the config you should have placed it either in `location @app` or `location /`. Or both, if an initial request may end up in both places. Also remember to restart or reload NGINX after making changes. – anemyte Jan 15 '21 at 20:01
  • I had put that line in the `location /` block, and now I have put it in both, but the error persists. I am in fact modifying the file _before_ kicking the server off with `exec /usr/bin/supervisord` which apparently starts Supervisor, "with Nginx and uWSGI". – mareoraft Jan 17 '21 at 15:36
  • Why are we convinced that **nginx** is the only issue here? What logging / troubleshooting steps can we take to see if nginx is receiving and blocking the request? It's very suspicious that the error message the browser gives says `localhost` instead of `162.243.168.182`. Let me see if the frontend is using the correct url... – mareoraft Jan 17 '21 at 15:44
  • Okay so we have progress... I fixed the frontend URL and now the error complains that there are TOO MANY Cors headers :D. `Access-Control-Allow-Origin: localhost:5001` and `Access-Control-Allow-Origin: *`. – mareoraft Jan 17 '21 at 16:02
  • Removing the **nginx** config changes gets it to work. Both frontend and backend docker containers are on the same server (and same IP address), but they are separate containers. Does this mean I no longer need `CORS` support in my Flask app at all? I added it during development because it was necessary, but perhaps it is not needed in PROD. Of course I can't use `*` in prod because it is insecure. – mareoraft Jan 17 '21 at 16:41
  • @mareoraft You do not need CORS when you have everything on the same origin (domain or IP-address) because it is not cross-origin anymore. The problem you faced was either bad header or no header (you could have checked response headers with dev tools in browser). My answer isn't fully correct here but now you probably can agree that setting up a web server to handle CORS is easier than flask. IMO CORS in flask might be handy when extra origins are changing constantly. In this example scenario you could benefit from flask by generating header value using a database or something like that. – anemyte Jan 17 '21 at 20:07
  • @mareoraft I guess I'll update the answer for anyone else who can get into this. – anemyte Jan 17 '21 at 20:08
  • Thanks again! Have a nice day! – mareoraft Jan 22 '21 at 15:09
2

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

  • 1
    From the link you've mentioned `add_header 'Access-Control-Allow-Origin' '*' always;` overcomes the problem with error codes. PS: have some rep from me :) – anemyte Jan 21 '21 at 09:22
1

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

Soroosh Khodami
  • 384
  • 3
  • 10
  • I used your suggested line (modifying `/api/` to the appropriate path), but I still obtain the CORS error. – mareoraft Jan 15 '21 at 18:23
  • 1
    Please see the additional information I have added to the question. – mareoraft Jan 15 '21 at 18:25
  • @mareoraft, use the /api/v1/ for version management of your APIs, it would be useful if you provide APIs for some mobile applications. you can release your new major APIs on /v2/ and keep the old APIs on /v1/ backward compatible. but the point was for other parameters like expose headers and .. in my python codes, please check them out. – Soroosh Khodami Jan 16 '21 at 06:25
  • @mareoraft I've updated my answer. – Soroosh Khodami Jan 17 '21 at 16:08
  • Thanks. I'm going to take a look tomorrow. My issue is not yet resolved but I am getting close. – mareoraft Jan 17 '21 at 16:43
  • 1
    Thanks again, have a nice day. – mareoraft Jan 22 '21 at 15:08