CORS#

Cross origin resources usually handled by browsers in specific way.

Sources

On this page we will look at an extremely simple example that shows where CORS errors occur and how to fix them using embedded fastapi tools. So we’ll look at a really popular case - there’s an API that expects to be accessed by a front-end running in a browser. So this is where CORS comes in - the browser won’t work with the results of the API response if it doesn’t have special headers.

import os
import docker
import requests
docker_client = docker.from_env()

Back-end#

The simplest possible backend application. It doesn’t use any middleware yet, to show that it won’t work if we just build it like this.

%%writefile cors_files/app.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def say_hello():
    return "hello"
Overwriting cors_files/app.py

Front-end#

Consider the front-end application that we’ll use as an example. The main feature is that here we have Fetch Data from Backend button that will cause fetching data from http://localhost:8000 where will be access to backend.

%%writefile cors_files/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Frontend</title>
</head>
<body>
    <h1>Frontend</h1>
    <button id="fetchData">Fetch Data from Backend</button>
    <pre id="response"></pre>

    <script>
        document.getElementById('fetchData').addEventListener('click', () => {
            fetch('http://localhost:8000')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('response').textContent = JSON.stringify(data, null, 2);
                })
                .catch(error => console.error('Error:', error));
        });
    </script>
</body>
</html>
Overwriting cors_files/index.html

Containers#

Now let’s set up Docker containers: one for the front-end and one for the back-end.

frontend_container = docker_client.containers.run(
    image = "httpd",
    remove = True,
    detach = True,
    name = "test_front",
    volumes = {
        f"{os.getcwd()}/cors_files/index.html": {
            "bind": "/usr/local/apache2/htdocs/index.html", 
            "mode": "rw"
        }
    },
    ports={80: 8080}
)

backend_container = docker_client.containers.run(
    image = "fastapi_experiment",
    remove = True,
    detach = True,
    name = "test_back",
    ports = {8000: 8000},
    volumes = {
        f"{os.getcwd()}/cors_files/app.py": {
            "bind": "/app.py", "mode": "rw"
        }
    },
    command = "uvicorn --host 0.0.0.0 --reload app:app"
)

Let’s check that the front-end is working by sending a request to it.

ans = requests.get("http://localhost:8080")
print(ans.content.decode("utf-8"))
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Frontend</title>
</head>
<body>
    <h1>Frontend</h1>
    <button id="fetchData">Fetch Data from Backend</button>
    <pre id="response"></pre>

    <script>
        document.getElementById('fetchData').addEventListener('click', () => {
            fetch('http://localhost:8000')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('response').textContent = JSON.stringify(data, null, 2);
                })
                .catch(error => console.error('Error:', error));
        });
    </script>
</body>
</html>

And same with back-end.

ans = requests.get("http://localhost:8000")
print(ans.content.decode("utf-8"))
"hello"

Problem#

Now you can access the frontend at http://localhost:8080. When you press the “Fetch data from backend” button, you’ll see something like the following picture:

pic

Your request failed because of CORS Missing Allow Origin.

Modification of the API#

To avoid the problem we had in the previous examples, we need to use fastapi.middleware.cors.CORSMiddleware. So here is a modification of the backend application.

%%writefile cors_files/app.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/")
def say_hello():
    return "hello"
Overwriting cors_files/app.py

If you now try to press the “Fetch data from backend” button - everything is fine.

Get cors headers#

What if we try to check if fastapi really adds cors? You need to add origin - only then will fastapi return CORS headers.


In the following example, we’re printing headers for our API request.

requests.get("http://localhost:8000").headers
{'date': 'Mon, 29 Jul 2024 15:51:57 GMT', 'server': 'uvicorn', 'content-length': '7', 'content-type': 'application/json'}

The result doesn’t contain any headers related to the cors.

But after adding the origin header to the request, we got headers with headers specific to CORS.

requests.get("http://localhost:8000", headers={'origin': ''}).headers
{'date': 'Mon, 29 Jul 2024 15:52:27 GMT', 'server': 'uvicorn', 'content-length': '7', 'content-type': 'application/json', 'access-control-allow-origin': '*', 'access-control-allow-credentials': 'true'}

So in the result we got headers access-control-allow-origin and access-control-allow-credentials.

Note don’t forget to stop containers after all.

frontend_container.stop()
backend_container.stop()