Flask RESTful cross-domain issue with Angular: PUT, OPTIONS methods

Question:

I’ve developed a small write-only REST api with Flask Restful that accepts PUT request from a handful of clients that can potentially have changing IP addresses. My clients are embedded Chromium clients running an AngularJS front-end; they authenticate with my API with a simple magic key — it’s sufficient for my very limited scale.

I’m testing deploying my API now and I notice that the Angular clients are attempting to send an OPTIONS http methods to my Flask service. My API meanwhile is replying with a 404 (since I didn’t write an OPTIONS handler yet, only a PUT handler). It seems that when sending cross-domain requests that are not POST or GET, Angular will send a pre-flight OPTIONS method at the server to make sure the cross-domain request is accepted before it sends the actual request. Is that right?

Anyway, how do I allow all cross-domain PUT requests to Flask Restful API? I’ve used cross-domaion decorators with a (non-restful) Flask instance before, but do I need to write an OPTIONS handler as well into my API?

Asked By: rgb

||

Answers:

You’re right, OPTIONS method called every time before real request in browser. OPTIONS response have allowed methods and headers. Flask automatically process OPTIONS requests.

To get access for cross domain request you API must have Access-Control-Allow-Origin header. It can contain specific domains, but if you want allow requests from any domains you can set it to Access-Control-Allow-Origin: *.

To set up CORS for flask you can look at one extension code or try use this extension: https://github.com/wcdolphin/flask-cors/blob/master/flask_cors.py.

To set up CORS for flask-restful look this pull requests: https://github.com/twilio/flask-restful/pull/122 and https://github.com/twilio/flask-restful/pull/131. But looks like flask-restful does not support it by default yet.

Answered By: tbicr

I resolved the issue by rewriting my Flask backend to answer with an Access-Control-Allow-Origin header in my PUT response. Furthermore, I created an OPTIONS handler in my Flask app to answer the options method by following what I read in the http RFC.

The return on the PUT method looks like this:

return restful.request.form, 201, {'Access-Control-Allow-Origin': '*'} 

My OPTIONS method handler looks like this:

def options (self):
    return {'Allow' : 'PUT' }, 200, 
    { 'Access-Control-Allow-Origin': '*', 
      'Access-Control-Allow-Methods' : 'PUT,GET' }

@tbicr is right: Flask DOES answer the OPTIONS method automatically for you. However, in my case it wasn’t transmitting the Access-Control-Allow-Origin header with that answer, so my browser was getting a reply from the api that seemed to imply that cross-domain requests were not permitted. I overloaded the options request in my case and added the ACAO header, and the browser seemed to be satisfied with that, and followed up OPTIONS with a PUT that also worked.

Answered By: rgb

With the Flask-CORS module, you can do cross-domain requests without changing your code.

from flask.ext.cors import CORS

app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

Update

As Eric suggested, the flask.ext.cors module is now deprecated, you should rather use the following code:

from flask_cors import CORS

app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
Answered By: Mathieu Rodic

You can use the after_request hook:

@app.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
    response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
    return response
Answered By: John Kenn

I like to use a decoration to solve.

def cross_origin(origin="*"):
    def cross_origin(func):
        @functools.wraps(func)
        def _decoration(*args, **kwargs):
            ret = func(*args, **kwargs)
            _cross_origin_header = {"Access-Control-Allow-Origin": origin,
                                    "Access-Control-Allow-Headers":
                                        "Origin, X-Requested-With, Content-Type, Accept"}
            if isinstance(ret, tuple):
                if len(ret) == 2 and isinstance(ret[0], dict) and isinstance(ret[1], int):
                    # this is for handle response like: ```{'status': 1, "data":"ok"}, 200```
                    return ret[0], ret[1], _cross_origin_header
                elif isinstance(ret, basestring):
                    response = make_response(ret)
                    response.headers["Access-Control-Allow-Origin"] = origin
                    response.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept"
                    return response
                elif isinstance(ret, Response):
                    ret.headers["Access-Control-Allow-Origin"] = origin
                    ret.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept"
                    return ret
                else:
                    raise ValueError("Cannot handle cross origin, because the return value is not matched!")
            return ret

        return _decoration

    return cross_origin

And then, Use decoration in your restful api.

class ExampleRestfulApi(Resource)
    @cross_origin()
    def get(self):
        # allow all cross domain access
        pass

    @cross_origin(origin="192.168.1.100")
    def post(self):
        # allow 192.168.1.100 access
        pass
Answered By: gordonpro

How about this workaround:

from flask import Flask
from flask.ext import restful
from flask.ext.restful import Api
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object('config')

#flask-sqlalchemy
db = SQLAlchemy(app)

#flask-restful
api = restful.Api(app)

@app.after_request

def after_request(response):
  response.headers.add('Access-Control-Allow-Origin', '*')
  response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
  response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
  return response

import views

I took this from this tutorial. Works very good. actually, i think this is the best approach I’ve seen so far.

Returning {'Access-Control-Allow-Origin': '*'} on each endpoint, doesn’t seems to be efficient since you have to add it on every endpoint. a bit anoying…, at least for me.

I tried @cors.crossdomain(origin='*') but, looks like it only works with GET request.

Answered By: Sebastian S

Just an update to this comment. Flask CORS is the way to go, but the flask.ext.cors is deprecated.

use:

from flask_cors import CORS

Answered By: Eric

To allow remote CORS requests on your web service api, you can simply initialize your flask restful API like this:

from flask import Flask
from flask_restful import reqparse, abort, Api, Resource
from flask_cors import CORS

app = Flask(__name__)
cors = CORS(app, resources={r"*": {"origins": "*"}})
api = Api(app)

This adds the CORS header to your api instance and allows a CORS request on every path from every origin.

Answered By: domih

I was facing the multiple types of CORS issue while connecting to my flask rest api from angular and tried almost all the approaches.
If you want to give access to all the websites without any restriction you can add below code in you app.py script :

from flask_cors import CORS , cross_origin

cors = CORS(app, resources={r"/*": {"origins": "*"}})

this will work, but its recommended to have some security which you can always edit in origin

Answered By: Rahul Samaiya