4

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!

sean
  • 3,094
  • 5
  • 23
  • 41
  • Sorry, it’s still not clear to me what you’re expecting. All of those cases in Edit 2 look to me like they are working as expected. Case #1 is one that you would consider working as expected too, right? In that one, you’ve not enabled CORS for that endpoint, so the browser is not allowing access to the response. Case #2 is also working as a expected as far as the CORS protocol goes, because you have set the allowed origin to `http://example.com` but you’re sending a request from `http://127.0.0.1:5000` so the browser does not let your code running at `http://127.0.0.1:5000` see the response – sideshowbarker Aug 07 '17 at 15:12
  • Case #3 is also working as expected as far as the CORS protocol goes—because with the configuration you are saying, “frontend code running at any origin is allowed to access responses from this endpoint”, so the code running at the origin you’re sending the request from successfully accesses the response. – sideshowbarker Aug 07 '17 at 15:15
  • I don’t understand what *“cause my api endpoints to mimic this behavior depending on what I set at the `origins=` setting”* means – sideshowbarker Aug 07 '17 at 15:16
  • My Flask endpoints are working like Case #3 regardless of whether the CORS extension is used, or whatever I include in the decorator with "origins=". They have never given me responses like Cases #1 and #2. Am I wrong to expect a Flask app to respond like those examples? – sean Aug 07 '17 at 15:24
  • I should clarify since I think two different things are going on in my question, and I made this confusing. Sideshowbarker, you are correct that the server won't block anything, as is shown in the curl response. That was my misunderstanding. However, I ran the same endpoint in JS in a browser, and it also does not behave like Case #1 (if I don't use CORS), or Case #2 (if I use the decorator with: "@cross_origin(origins=['http://example.com'])"). So it is still behaving strangely even though my original question is confusing. I'll edit that curl part since that was a bad example. – sean Aug 07 '17 at 16:42
  • As far as the case where a server responds to a CORS preflight OPTIONS request with a 403, that response would be wrong. The response to a CORS preflight OPTIONS request should *never* be a 403. So any server that responds with a 403 to a preflight OPTIONS is broken, as far as the CORS protocol goes. The server should instead respond to the OPTIONS with a 200 or 204, always. But if the server doesn’t want to allow cross-origin requests, it should just not send the Access-Control-Allow-Origin in that response, & not send the Access-Control-Allow-Headers or Access-Control-Allow-Methods headers – sideshowbarker Aug 07 '17 at 20:24
  • Also, unless there’s some characteristic of your request that triggers browsers to do a preflight, then your browser won’t make a preflight OPTIONS request to begin with but would instead just make whatever request your code is trying to send. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests for details about what kinds of requests trigger browsers to do a preflight. Also you might consider updating your question to add the actual frontend JavaScript code you’re using to make the request – sideshowbarker Aug 07 '17 at 20:28
  • At this point is sounds like what you’re saying is happening is that the server is sending a `Access-Control-Allow-Origin: *` response header rather than the `Access-Control-Allow-Origin: http://example.com` response header you’d expect based on your config. If that’s the case, I don’t know why that would be happening. But one point to be clear about: if the server you’re testing with is the same server you’re making the request to, then none of this matters anyway, because in that case it’s not a cross-origin request so the browser allows you to get to the response regardless – sideshowbarker Aug 07 '17 at 20:39

1 Answers1

5

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.

sideshowbarker
  • 62,215
  • 21
  • 143
  • 153
  • Thanks for your help on this. Perhaps I can explain using an example. I set up an API Gateway endpoint on AWS and tried to access it without enabling CORS. It returns this response: "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://another-example.com' is therefore not allowed access." I am basically trying to get my Flask app to send that response when CORS has not been enabled in the app, but it always allows any access, even from an ajax request from js in a browser. I thought I understood CORS, but perhaps a server setting is missing? – sean Aug 07 '17 at 12:10
  • To put this in simpler terms, it seems that my application has no Same-Origin Policy at all. I'm not very experienced with infrastructure, but I assumed this was enabled as a default. So far, Google searches haven't helped much concerning enabling this policy. – sean Aug 07 '17 at 13:34
  • My comments above are misleading / unclear, so I added additional info under "EDIT 2" in my question. Thanks again for your help. – sean Aug 07 '17 at 14:46
  • As far as *“trying to get my Flask app to send that response when CORS has not been enabled in the app, but it always allows any access, even from an ajax request from js in a browser”* the examples added to the question don’t indicate “it always allows any access, even from an ajax request from js in a browser”—instead they indicate the *browser* is blocking access to the response as expected in conformance with the Access-Control-Allow-Origin header the server is sending. Are you still expecting the server itself to do some kind of blocking? The server itself *never* does any blocking, ever – sideshowbarker Aug 07 '17 at 15:21
  • EDIT 2's examples aren't from my Flask app. They are example endpoints on AWS API Gateway. Yes, they are what I'd expect would happen, but my Flask app is not behaving in the same way – sean Aug 07 '17 at 16:27
  • Added clarification in the original question & comment. I made this confusing by bringing in the curl example. – sean Aug 07 '17 at 16:50
  • You might consider updating your question to include the exact response(s) you are actually getting in your browser for the request you’re actually making from your frontend JavaScript code. What I mean is, go into the Network pane in your browser devtools and examine the response(s) and copy and paste the response headers into your question. If the request you’re making is actually cross-origin—and not from the same origin as the server the request is being sent to—then I think what you will find is, the server’s sending an Access-Control-Allow-Origin response header with the `*` wildcard – sideshowbarker Aug 07 '17 at 20:35
  • thanks for all the help! Please see my "SOLUTION EDIT" for how this issue was resolved. Your assistance was very helpful - I understand CORS quite a bit better now (the curl correction was very helpful). Thanks! – sean Aug 08 '17 at 00:15