Search code examples
pythonreact-reduxcorspymongotornado

API headers not working in Python-Tornado when front end makes requests


I've got a basic CRUD application, and I'm trying to get React-Redux on localhost:3000 to make requests to the backend, Python-Tornado (using pymongo for the database) at 127.0.0.1:8888. At first CORS was blocking all requests, but I found online that you can add headers to allow access to the API, so I did like this:

class UserHandler(tornado.web.RequestHandler):

    def set_default_headers(self):
        self.set_header("Access-Control-Allow-Origin", "*")
        self.set_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, 
        Content-Type, Accept, Authorization")
        self.set_header('Access-Control-Allow-Methods', 
        'GET,HEAD,OPTIONS,POST,PUT,DELETE')
        self.set_status(204)

    def get(self, id=None):
        {...}

    def post(self):
        {...}

    def delete(self, id):
        {...}

    def put(self, id):
        {...}

def make_app():
    return tornado.web.Application([
        (r"/users/(?P<id>\w+)", UserHandler),
        (r"/user/(?P<id>\w+)", UserHandler),
        (r"/users", UserHandler)
    ],
    debug = True,
    autoreload = True)

if __name__ == "__main__":
    app = make_app()
    port = 8888
    app.listen(port)
    print(f"🌐 Server is listening on port {8888}")
    #start server on current thread
    tornado.ioloop.IOLoop.current().start()

Now my Front end is allowing GET requests, but POST, PUT, and DELETE are still getting blocked with the following error:

Access to XMLHttpRequest at 'http://127.0.0.1:8888/users/625f7194a10940f94e0b8f13' 
from origin 'http://localhost:3000' has been blocked by CORS policy: Response to 
preflight request doesn't pass access control check: The 'Access-Control-Allow- 
Origin' header has a value 'https://localhost:3000' that is not equal to the supplied 
origin.

I've looked around at multiple resources and they all say to simply add those headers, so I'm not sure where to go from here.

Any help is appreciated, thanks!


Solution

  • The answer by Code-Apprentice is correct: the request origin doesn't match the value in Access-Control-Allow-Origin header.

    However, to keep things easier during development (while in debug mode), you can always allow the current request origin or just set wildcard values. This will also be useful if you want to test from multiple devices (as you won't need to reconfigure the origin header).

    This is what I do in my projects:

    class MyHandler(web.RequestHandler):
        def set_default_headers(self):
            if self.application.settings.get('debug'): # debug mode is True
                self.set_dev_cors_headers()
    
        def set_dev_cors_headers(self):
            # For development only
            # Not safe for production
            origin = self.request.headers.get('Origin', '*') # use current requesting origin
            self.set_header("Access-Control-Allow-Origin", origin)
            self.set_header("Access-Control-Allow-Headers", "*, content-type, authorization, x-requested-with, x-xsrftoken, x-csrftoken")
            self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT, PATCH')
            self.set_header('Access-Control-Expose-Headers', 'content-type, location, *, set-cookie')
            self.set_header('Access-Control-Request-Headers', '*')
            self.set_header('Access-Control-Allow-Credentials', 'true')
    
        def options(self, *args, **kwargs):
            # also set a 204 status code for OPTIONS request
            if self.application.settings.get('debug'):
                self.set_status(204)
            else:
                # perhaps do some checks in production mode
                # before setting the status code
                # ...
                self.set_status(204)
            self.finish()