Search code examples
javascriptpythoncorsfastapilocal

How to enable CORS in FastAPI for local HTML file loaded via a file:/// URL?


I am trying to enable CORS in FastAPI on my localhost with credentials enabled. According to the docs we must explicitly set allow_origins in this case:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware, Response

app = FastAPI()
app.add_middleware(
    CORSMiddleware, allow_origins=['http://localhost:8000'],
    allow_credentials=True, allow_methods=['*'], allow_headers=['*'])

@app.get('/')
def main():
    return Response('OK', status_code=200)

However, when making a request it does fail with

CORS Missing Allow Origin [Cross-source (cross-origin) request blocked: The same origin policy disallows reading the remote resource at http://[::1]:8000/create_session/none. (Reason: CORS header 'Access-Control-Allow-Origin' is missing). Status code: 200.]

const response = await fetch('http://localhost:8000/', {credentials: "include"});

The client is Firefox with a local file (file://.../main.html) opened.

I already tried all solutions from How can I enable CORS in FastAPI?. What's wrong?

Edit: My question is not a duplicate of How to access FastAPI backend from a different machine/IP on the same local network?, because the server and the client are on the same (local)host. Nevertheless I tried setting the suggested --host 0.0.0.0 and the error remains the same.

Also it's not a duplicate of FastAPI is not returning cookies to React frontend, because it suggests setting the CORS origin in the same way as the official docs, which does not work as I described above.


Solution

  • Since you haven't provided the actual CORS error in your question, it should be specified here for future readers visiting this post. When sending a JavaScript HTTP request to a server, in this case a FastAPI backend, through a local HTML file that has been loaded into the browser via a file:/// URL—e.g., by dragging-dropping the file into the browser or just double-clicking the file—with the URL in the browser's address bar looking like this:

    file:///C:/...index.html
    

    browsers that apply CORS would output the following errors. If you are using Chrome (or a chromium-based) browser, you would see a CORS error (Cross-Origin Resource Sharing error: MissingAllowOriginHeader) under the Status column in the Network tab/section, indicating that the response to the CORS request is missing the required Access-Control-Allow-Origin header, in order to determine whether or not the resource can be accessed. The error is made more clear by looking at the console, after submitting the request:

    Access to fetch at 'http://localhost:8000/' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

    GET http://localhost:8000/ net::ERR_FAILED 200 (OK)

    If you are using FireFox instead, you would see a similar error in the console:

    Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8000/. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

    Status code: 200

    The reason for the error(s) raised above is that you are trying to perform a cross-origin request through a script that is running via a file:/// URL. In that case, the client's origin is null, but you only added http://localhost:8000 to the allowed origins. Thus, a quick fix would be to add null to the origins list, e.g., Access-Control-Allow-Origin: null. In FastAPI, you could do that as shown in the example below (however, it is not recommended to do so—see more details below the example).

    Working Example

    app.py

    from fastapi import FastAPI
    from fastapi.middleware.cors import CORSMiddleware
    
    app = FastAPI()
    
    origins = ['null']  # NOT recommended - see details below
              
    app.add_middleware(
        CORSMiddleware,
        allow_origins=origins,
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
    
    @app.get('/')
    def main():
        return 'ok'
    

    index.html

    <!DOCTYPE html>
    <html>
       <body>
          <h1>Send JS request</h1>
          <button onclick="getData()">Click me</button>
          <script>
             function getData() {
                fetch('http://localhost:8000/')
                   .then(resp => resp.json()) // or resp.text(), etc.
                   .then(data => {
                      console.log(data);
                   })
                   .catch(error => {
                      console.error(error);
                   });
             }
          </script>
       </body>
    </html>
    

    Note

    However, it is not recommended doing that, as it is insecure. As described in MDN's documentation:

    Note: null should not be used: "It may seem safe to return Access-Control-Allow-Origin: "null", but the serialization of the Origin of any resource that uses a non-hierarchical scheme (such as data: or file:) and sandboxed documents is defined to be "null". Many User Agents will grant such documents access to a response with an Access-Control-Allow-Origin: "null" header, and any origin can create a hostile document with a "null" Origin. The "null" value for the ACAO header should therefore be avoided."

    Another great article on the risks of using the null origin, as well as misconfiguring CORS in general, can be found here.

    As explained in several related questions, as shown here and here, one should rather serve the HTML file(s), e.g., index.html above, using a local web server. FastAPI can do that for you as well, using the HTMLResponse or even Jinja2Templates, as demonstrated in this answer, this answer, as well as this, this and this. Thus, I would strongly recommend having a look at those posts and serve your HTML file(s) through FastAPI, which is pretty straightforward, instead of adding null to the origins.

    Related answers on CORS can also be found here and here.