Area 08 // BackendLesson 131 / 14011 min read
CORS
CORS (cross-origin resource sharing) is the browser rule that decides whether a web page from one origin may read responses from an API on another origin, and your API opts in by sending headers that list exactly which origins, methods and headers it allows.
Think of it like this
Analogy
A building's reception desk (the browser) has a rule: visitors from Company A can't collect parcels addressed to Company B unless Company B has left a note saying "Company A may collect our parcels". The note is the CORS header. It protects tenants from strangers walking off with their mail, but it does nothing to stop a courier going straight to Company B's back door: that's what locks (authentication) are for.
The problem it solves
You deploy your chat frontend at https://app.example.com and your API at https://api.example.com (or, locally, localhost:3000 and localhost:8000). The first request fails in the browser with a "blocked by CORS policy" error, even though the API works perfectly from curl. The tempting fix, allowing every origin, can let any website use your users' logged-in sessions to read their data.
How it works, step by step
- Same-origin policy: browsers let a page freely read responses only from its own origin (scheme + host + port).
- Cross-origin requests are allowed to be sent, but the page can only read the response if the API says so with
Access-Control-Allow-Originmatching the page's origin. - Preflight: for "non-simple" requests (JSON
Content-Type, anAuthorizationheader, methods like PUT or DELETE) the browser first sendsOPTIONSwithOrigin,Access-Control-Request-MethodandAccess-Control-Request-Headers; the API replies with what it allows. - Credentials (cookies) need
Access-Control-Allow-Credentials: trueand an exact origin, never*. - Configure it in one place: FastAPI's
CORSMiddlewarewith exact allowed origins per environment (from config), the methods and headers you use. - Remember what it isn't: CORS doesn't authenticate anyone or stop non-browser clients; you still need authentication and rate limiting.
- Alternative: serve the frontend and API from the same origin (e.g.
/apibehind the same domain via a proxy) and CORS isn't needed at all.
See it in code
- Install
pip install fastapi httpx- Needs
- Python 3.10+. TestClient sends the same headers a browser would.
A preflight from the allowed frontend vs another site, and why CORS isn't a security wall for the server.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.testclient import TestClient
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"], # exact origins of YOUR frontend, not "*"
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"],
allow_credentials=True,
)
@app.post("/ask")
def ask():
return {"answer": "..."}
c = TestClient(app)
for origin in ["https://app.example.com", "https://evil.example.net"]:
# The browser sends this "preflight" before a cross-origin POST with JSON and an auth header.
r = c.options("/ask", headers={"Origin": origin, "Access-Control-Request-Method": "POST",
"Access-Control-Request-Headers": "authorization,content-type"})
print(f"preflight from {origin:26} -> {r.status_code}, allow-origin: {r.headers.get('access-control-allow-origin')}")
r = c.post("/ask", headers={"Origin": "https://evil.example.net"})
print("actual POST from evil origin ->", r.status_code, "| allow-origin header:", r.headers.get("access-control-allow-origin"))
print("note: the server still answered; CORS only tells BROWSERS whether the page may read the response")preflight from https://app.example.com -> 200, allow-origin: https://app.example.com
preflight from https://evil.example.net -> 400, allow-origin: None
actual POST from evil origin -> 200 | allow-origin header: None
note: the server still answered; CORS only tells BROWSERS whether the page may read the responseThe last two lines are the key lesson: the server processed the request anyway. A browser would hide the response from the evil page, but a script would read it, so authentication is what really protects the API.
A real example
Example
The wildcard that leaked chat history
A startup's API used cookie sessions and, to fix a CORS error during a demo, reflected any Origin back with credentials allowed. A security researcher showed that any website a logged-in user visited could fetch /conversations in the background and read their chat history. The fix was an explicit allow-list (https://app.<their-domain> in production, localhost ports in development, loaded from config), SameSite cookies, and moving the frontend and API under one domain so most requests weren't cross-origin at all.
The easy way
Start here
Add CORSMiddleware with the exact origin of your frontend (and your local dev URL in development), the methods and headers you actually use, and nothing more. Or serve the frontend and API from the same domain and skip CORS.
When you grow: the production setup
Trade-offs
What you gain
- Protects users' browsers from malicious sites reading their data.
- Simple to configure in one middleware.
- Lets frontends and APIs live on separate domains.
What it costs you
- Confusing errors that only appear in browsers.
- Preflights add a round trip.
- Misconfiguration can silently open data to any site.
When not to use it
- APIs called only server-to-server don't need CORS headers at all.
Common mistakes
allow_origins=["*"](or reflecting any origin) with credentials.- Thinking CORS protects the API from scripts or bots.
- Forgetting the dev origin's port is part of the origin.
- Adding CORS headers in several places (proxy and app) that conflict.
- Allowing every method and header "to make it work".
Interview questions
Try answering out loud first, then open the model answer.
What is CORS and why does it exist?
Model answer
A browser mechanism that relaxes the same-origin policy in a controlled way: a page from one origin may read responses from another origin only if that server opts in with Access-Control-Allow-* headers. It exists to stop malicious sites from using a user's browser (and their cookies) to read data from other sites.
What triggers a preflight request?
Model answer
Cross-origin requests that aren't "simple": methods other than GET, HEAD or POST; custom headers such as Authorization; or a Content-Type other than form or plain text (e.g. application/json). The browser sends OPTIONS first and only sends the real request if the response allows it.
Does a correct CORS setup secure your API?
Model answer
No. CORS is enforced by browsers only; any script, server or tool can call your API regardless. Security comes from authentication, authorization, rate limiting and input validation; CORS just prevents other websites from reading responses in your users' browsers.
Quiz
Pick an answer to see whether it’s right and why.
0 of 5 answered
Key terms
- CORS (cross-origin resource sharing)
- The browser mechanism that lets a page from one origin read responses from another origin only if that server allows it with special headers.
- Origin
- The combination of scheme, host and port of a web page or API, such as https://app.example.com. Different ports mean different origins.
- Preflight request
- An OPTIONS request a browser sends before certain cross-origin requests to ask the server whether the real request is allowed.
- Same-origin policy
- The browser rule that pages can freely read responses only from their own origin; CORS relaxes it in a controlled way.
- HTTP header
- A name and value sent with a request or response to carry metadata, such as Authorization, Content-Type or Retry-After.
Before and after
Progress is saved in this browser only.