FastAPI: Access to fetch blocked by CORS policy

When using FastAPI, this error can occur when your frontend content is being served from a different server (or port) than your backend server. To fix this, import CorsMiddleware from starlette.middleware.cors and add it to your app.

What is CORS?

CORS stands for “Cross-Origin Request Sharing”. It’s what allows scripts in your browser to safely (in theory) make requests to a different host other than the one your webpage was loaded from. Images, iframes, and videos, for example, can be embedded in a web page from anywhere with no issue. Scripts, on the other hand, can’t just make AJAX requests to any server they want. They need to be allowed by the CORS policy the browser gets from the server.

When do you have to worry about CORS?

It comes down to whether or not your frontend is making requests to a different host or not. A different host, at least according to the HTTP protocol, can be a different subdomain, a different port, or a completely different domain. It doesn’t matter if the services are, in reality, running on the same physical or virtual server.

Your API is a path on the same domain? No.

It’s common for frontend web content to be served from a different server than a backend API. In production, these two (or more) servers usually share a domain name and are mounted at different paths on that domain name using a reverse proxy such as Nginx or Traefik. In this case, you don’t need to worry about CORS, because you’re accessing the API from the same host as the web content.

For example:

  • Web Content: https://example.com
  • API Server: https://example.com/api/

Your API is on a subdomain? Yes.

Sometimes, however, you can have your API server running on a subdomain, which is also very common. Big sites like Amazon and Google do this. If you’re also doing it like that, you do need to worry about CORS, because a subdomain is considered a different host.

  • Web Content: https://example.com
  • API Server: https:api.example.com

Your API is on a different port? Yes.

Another common scenario is when your app is in development. Often, when we’re developing our app’s services, we have them running on the same host but on different ports. In the eyes of your web browser, these are considered different hosts.

Example:

  • Web Content: http://localhost:3000
  • API Server: http://localhost:8000

Your API is on a different domain name entirely? Very yes.

If your app also provides an API that can be accessed by third parties from entirely different domains, this would also constitute different hosts, and you would need to implement a CORS policy on your server.

Example:

  • 3rd Party: https://somecompletelydifferentsite.com
  • API Server: https://yourapp.com/api/

Of course, in this case, you’re going to want more than a CORS policy to lock down your API. You’ll probably want to also look into IAM services and access tokens.

Problem: your frontend is served from different host than your backend

Now that we’ve covered what it means for your services to be on different hosts, let’s look at a situation where your FastAPI service is on a different port from where your React app is being served. The code we’re using is from the project we made in the full stack GraphQL example, but its applicability isn’t limited to GraphQL apps.

The backend FastAPI server

Below is our FastAPI server:

import os
import uvicorn
import strawberry
from strawberry.fastapi import GraphQLRouter
from fastapi import FastAPI

from gql import Query, Mutation

app = FastAPI()

schema = strawberry.Schema(query=Query, mutation=Mutation)
app.include_router(GraphQLRouter(schema), prefix="/graphql")

if __name__ == "__main__":
    prod = os.getenv("PROD") == "1"
    uvicorn.run("main:app", host="0.0.0.0", reload=not prod)
Python

We’re not specifying a port, so the server is running on uvicorn’s default, port 8000.

The frontend React app

Below is our URQL config in the React app on the frontend.

If you’re unfamiliar, URQL is just a GraphQL client that works with React, so don’t get too caught up in that if you’re not experienced with GraphQL. The /graphql path it’s accessing is just another FastAPI endpoint. If you’re interested in learning GraphQL, check out our full stack FastAPI/GraphQL example.

import { createClient, defaultExchanges } from "urql";

export const urqlClient = createClient({
    url: `http://${process.env.REACT_APP_HOSTNAME}:8000/graphql`,
    exchanges: [...defaultExchanges]
});
JavaScript

For the above example, assume we used create-react-app, and are running the development server on the default as well, port 3000.

Below is the React component that’s displaying data from the server:

import { useQuery } from "urql";

const GET_MESSAGE = `
    query {
        message {
            text
        }
    }
`;

export default function Message() {
    const [{data, fetching}] = useQuery({query: GET_MESSAGE});

    if (fetching) {
        return (
            <h1>Loading...</h1>
        );
    } else {
        return (
            <h1>{data?.message.text}</h1>
        );
    }
}
JavaScript

And finally, this is what shows up when we try to bring up our services and access the frontend:

The data is never fetched, and, if you check out the dev tools console, you’ll see that you get a CORS error.

Solution: Import the CORS middleware and add it to your FastAPI app

To fix this, we need to import the CORSMiddleware class from starlette.middleware.cors, then add it to our app:

import os
import uvicorn
import strawberry
from strawberry.fastapi import GraphQLRouter
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

from gql import Query, Mutation

app = FastAPI()

app.add_middleware(
            CORSMiddleware,
            allow_origins=[f"http://localhost:3000"],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"]
        )

schema = strawberry.Schema(query=Query, mutation=Mutation)
app.include_router(GraphQLRouter(schema), prefix="/graphql")

if __name__ == "__main__":
    prod = os.getenv("PROD") == "1"
    uvicorn.run("main:app", host="0.0.0.0", reload=not prod)
Python

To add it to our app, we use app.add_middleware.

We pass in the following arguments:

  • The class itself (do not instantiate it)
  • allow_origins set to our frontend URL
  • allow_credentials set to True – this doesn’t apply to our example, but it would allow the frontend to use basic HTTP credentials if your app is using them
  • allow_methods set to a list with just "*" as the only member – this means we want to allow GET, POST, PUT, etc. as the REST methods allowed from a different host. Since this example uses GraphQL, we really could lock this down to just POST since every GQL request is made that way.
  • allow_headers also set to “*” – we don’t care what HTTP headers the client sends us

Once we do this and launch the app again, we should see that it’s working and we no longer get a CORS error:

Conclusion

In this article, covered what causes CORS errors in FastAPI applications, what CORS is, when you should worry about it, and how to implement a CORS policy in your FastAPI app so that you can hit the API from a different host.

To summarize, you need to implement CORS via the CORSMiddleware class in FastAPI if you’re hosting your API on a subdomain, different domain, or port from where your frontend is served.