corydolphin / flask-cors

Cross Origin Resource Sharing ( CORS ) support for Flask
https://flask-cors.corydolphin.com/
MIT License
867 stars 140 forks source link

[Feature Suggestion] Configurable response status code to invalid CORS request #326

Open lavenderses opened 1 year ago

lavenderses commented 1 year ago

Configurable CORS response code when CORS request is invalid

Thank you for taking look at this PR!

In summary

The default behaviour is SAME as current behaviour.

Details

Background

According to CORS document from WHATWG, CORS protocol can take any response code to CORS request as long as response header contains CORS headers like Access-Control-Allow-Origin. It does not specify response code and response body.

A successful HTTP response, i.e., one where the server developer intends to share it, to a CORS request can use any status, as long as it includes the headers stated above with values matching up with the request.

A successful HTTP response to a CORS-preflight request is similar, except it is restricted to an ok status, e.g., 200 or 204.

But, there might be some wishes like 'Not to return response to invalid CORS request' for security'. From the WHATWG doc,

Any other kind of HTTP response is not successful and will either end up not being shared or fail the CORS-preflight request. Be aware that any work the server performs might nonetheless leak through side channels, such as timing. If server developers wish to denote this explicitly, the 403 status can be used, coupled with omitting the relevant headers.

So I implemented the feature.

Feature

Add invalid_cors_status_code argument for CORS configuration to flask_cors.CORS and flask_cors.cross_origin.

Note:

  1. Now, Flask-Cors responses to the CORS request with status 200 and response body. To maintain the backward compatibility, the argument default value is 200 and does not change response body when invalid_cors_status_code is not passed or passed 200.

  2. Personally, response code to the invalid CORS request should be 403 like Spring Security or 401 if it should be authenticated. At least Client Error Response Code in Mozilla HTPP response code doc should be returned, which is from 400 to 499. So, by setting variable INVALID_CORS_STATUS_MIN = 400 and INVALID_CORS_STATUS_MAX = 499 in flask_cors/core.py, this feature filters invalid_cors_status_code. If other value like 302 is set to invalid_cors_status_code, this sets response status 200(set in INVALID_CORS_DEFAULT_STATUS) and response body ''(set in INVALID_CORS_RESPONSE_DATA).

Checked with nosetests --with-coverage --cover-package=flask_cors.

Thank you so much!

lavenderses commented 1 year ago

@corydolphin Here are some examples!

Befor change

from flask import Flask, jsonify
from flask_cors import CORS

app = Flask('FlaskCorsAppBasedExample')

CORS(app, origins=['https://valid.com'])

@app.route("/api/v1/users/")
def list_users():
    return jsonify(user="joe")

if __name__ == "__main__":
    app.run()

Valid CORS request

# Returns 'Access-Control-Allow-Origin' header

$ curl -v -H 'Origin: https://valid.com' http://localhost:5000/api/v1/users/
> GET /api/v1/users/ HTTP/1.1
> Host: localhost:5000
> Origin: https://valid.com
> 

< HTTP/1.1 200 OK
< Content-Type: application/json
< Access-Control-Allow-Origin: https://valid.com
< 
{
  "user": "joe"
}

Invalid CORS request

# DOES NOT returns 'Access-Control-Allow-Origin' header

$ curl -v -H 'Origin: https://INVALID.com' http://localhost:5000/api/v1/users/
> GET /api/v1/users/ HTTP/1.1
> Host: localhost:5000
> Origin: https://INVALID.com

< HTTP/1.1 200 OK
< Content-Type: application/json
< 
{
  "user": "joe"
}

After change

from flask import Flask, jsonify

from flask_cors import CORS

app = Flask('FlaskCorsAppBasedExample')

# Add 'invalid_cors_status_code' argumemnt
CORS(app, origins=['https://valid.com'], invalid_cors_status_code=403)

@app.route("/api/v1/users/")
def list_users():
    return jsonify(user="joe")

if __name__ == "__main__":
    app.run()

Valid CORS request

# Returns 'Access-Control-Allow-Origin' header

$ curl -v -H 'Origin: https://valid.com' http://localhost:5000/api/v1/users/
> GET /api/v1/users/ HTTP/1.1
> Host: localhost:5000
> Origin: https://valid.com

< HTTP/1.1 200 OK
< Content-Type: application/json
< Access-Control-Allow-Origin: https://valid.com
{
  "user": "joe"
}

Invalid CORS request

# RETURNS 403(FORBIDDEN) and empty content

curl -v -H 'Origin: https://INVALID.com' http://localhost:5000/api/v1/users/
> GET /api/v1/users/ HTTP/1.1
> Host: localhost:5000
> Origin: https://INVALID.com

< HTTP/1.1 403 FORBIDDEN
< Content-Type: application/json