Python Flask CORS - API always allows any origin

sean picture sean · Aug 7, 2017 · Viewed 13k times · Source

I've looked through many SO answers, and can't seem to find this issue. I have a feeling that I'm just missing something obvious.

I have a basic Flask api, and I've implemented both the flask_cors extension and the custom Flask decorator [@crossdomain from Armin Ronacher].1 (http://flask.pocoo.org/snippets/56/) Both show the same issue.

This is my example app:

application = Flask(__name__,
            static_url_path='',
            static_folder='static')
CORS(application)
application.config['CORS_HEADERS'] = 'Content-Type'

@application.route('/api/v1.0/example')
@cross_origin(origins=['http://example.com'])
# @crossdomain(origin='http://example.com')
def api_example():
  print(request.headers)
  response = jsonify({'key': 'value'})
  print(response.headers)
  return response

(EDIT 3 inserted):

When I make a GET request to that endpoint from JS in a browser (from 127.0.0.1), it always returns 200, when I would expect to see:

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:5000' is therefore not allowed access. The response had HTTP status code 403.

CURL:

ACCT:ENVIRON user$ curl -i http://127.0.0.1:5000/api/v1.0/example
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 20
Access-Control-Allow-Origin: http://example.com
Server: Werkzeug/0.11.4 Python/2.7.11
Date: [datetime]

{
  "key": "value"
}

LOG:

Content-Length: 
User-Agent: curl/7.54.0
Host: 127.0.0.1:5000
Accept: */*
Content-Type: 


Content-Type: application/json
Content-Length: 20


127.0.0.1 - - [datetime] "GET /api/v1.0/example HTTP/1.1" 200 -

I'm not even seeing all of the proper headers in the response, and it doesn't seem to care what the origin is in the request.

Any ideas what I'm missing? Thanks!


EDIT:

As a side note, looking at the documentation example here (https://flask-cors.readthedocs.io/en/v1.7.4/#a-more-complicated-example), it shows:

@app.route("/")
def helloWorld():
    '''
        Since the path '/' does not match the regular expression r'/api/*',
        this route does not have CORS headers set.
    '''
    return '''This view is not exposed over CORS.'''

...which is rather interesting since I already have the root path (and others) exposed without any CORS decoration, and they are working fine from any origin. So it seems that there is something fundamentally wrong with this setup.

Along those lines, the tutorial here (https://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask) seems to indicate that Flask apis should naturally be exposed without protection (I would assume that's just since the CORS extension hasn't been applied), but my application is basically just operating like the CORS extension doesn't even exist (other than a few notes in the log that you can see).


EDIT 2:

My comments were unclear, so I created three example endpoints on AWS API Gateway with different CORS settings. They are GET method endpoints that simply return "success":

1) CORS not enabled (default):

Response:

XMLHttpRequest cannot load https://t9is0yupn4.execute-api.us-east-1.amazonaws.com/prod/cors-default. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:5000' is therefore not allowed access. The response had HTTP status code 403.

2) CORS enabled - Origin Restricted:

Response:

XMLHttpRequest cannot load https://t9is0yupn4.execute-api.us-east-1.amazonaws.com/prod/cors-enabled-example. Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'http://example.com' that is not equal to the supplied origin. Origin 'http://127.0.0.1:5000' is therefore not allowed access.

3) CORS enabled - Origin Wildcard:

Response:

"success"

I'm not that experienced with infrastructure, but my expectation was that enabling the Flask CORS extension would cause my api endpoints to mimic this behavior depending on what I set at the origins= setting. What am I missing in this Flask setup?


SOLUTION EDIT:

Alright, so given that something on my end was obviously not normal, I stripped down my app and re-implemented some very basic APIs for each variation of CORS origin restriction. I've been using AWS's elastic beanstalk to host the test environment, so I re-uploaded those examples and ran a JS ajax request to each. It's now working.

I'm getting the Access-Control-Allow-Origin error on naked endpoints. It appears that when I configured the app for deployment I was uncommenting CORS(application, resources=r'/api/*'), which was obviously allowing all origins for the naked endpoints!

I'm not sure why my route with a specific restriction (origins=[]) was also allowing everything, but that must have been some type of typo or something small, because it's working now.

A special thanks to sideshowbarker for all the help!

Answer

sideshowbarker picture sideshowbarker · Aug 7, 2017

From your question as-is, it’s not completely clear what behavior you’re expecting. But as far as how the CORS protocol works, it seems like your server is already behaving as expected.

Specifically, the curl response cited in the question shows this response header:

Access-Control-Allow-Origin: http://example.com

That indicates a server already configured to tell browsers, Only allow cross-origin requests from frontend JavaScript code running in browsers if code’s running at the origin http://example.com.

If the behavior you’re expecting is that the server will now refuse requests from non-browser clients such as curl, then CORS configuration on its own isn’t going to cause a server to do that.

The only thing a server does differently when you configure it with CORS support is just to send the Access-Control-Allow-Origin response header and other CORS response headers. That’s it.

Actual enforcement of CORS restrictions is done only by browsers, not by servers.

So no matter what server-side CORS configuration you make, the server still goes on accepting requests from all clients and origins it would otherwise; in other words, all clients from all origins still keep on getting responses from the server just as they would otherwise.

But browsers will only expose responses from cross-origin requests to frontend JavsScript code running at a particular origin if the server the request was sent to opts-in to permitting the request by responding with an Access-Control-Allow-Origin header that allows that origin.

That’s the only thing you can do using CORS configuration. You can’t make a server only accept and respond to requests from particular origins just by doing any server-side CORS configuration. To do that, you need to use something other than just CORS configuration.