Course Content
FastAPI Essentials
1 sections · 32 lessons
Can you describe how you would handle CORS (Cross-Origin Resource Sharing) in FastAPI?
What you need to know
By default a browser lets a page send a request to another origin but does not let the page's JavaScript read the response. The server opts in with Access-Control-Allow-* headers. For requests with JSON bodies, custom headers or methods other than GET/POST, the browser first sends a preflight OPTIONS request asking "may I?".
1from fastapi import FastAPI2from fastapi.middleware.cors import CORSMiddleware34app = FastAPI()5app.add_middleware(6 CORSMiddleware,7 allow_origins=["https://app.example.com"], # from config, per environment8 allow_credentials=True, # only if you use cookies9 allow_methods=["GET", "POST"],10 allow_headers=["authorization", "content-type"],11 expose_headers=["x-request-id"], # let browser JS read this header12 max_age=600, # cache the preflight for 10 minutes13)Real results with this configuration:
preflight from https://app.example.com -> 200, Access-Control-Allow-Origin: https://app.example.compreflight from https://evil.example -> 400, no allow-origin headerPOST from https://evil.example -> 200 {'answer': 'hi'}, no allow-origin headerThe last line is the key lesson. The server still ran the request; only the browser refused to show the response to the evil page's JavaScript. CORS protects users' browsers from reading data, not your server from being called.
The wildcard trap
The CORS standard forbids Access-Control-Allow-Origin: * together with credentials. Starlette handles allow_origins=["*"] plus allow_credentials=True by echoing back whatever origin asked:
allow_origins=["*"], allow_credentials=True, request from https://evil.example-> Access-Control-Allow-Origin: https://evil.example Access-Control-Allow-Credentials: trueSo the request does not fail — it succeeds for every website, with the user's cookies. That is effectively no CORS protection for cookie-authenticated APIs. Always list origins explicitly when credentials are on.
Other gotchas
- Custom response headers such as
x-request-idare invisible to browser JavaScript unless listed inexpose_headers. - A failed preflight appears in the browser console as a vague network error. Check the
OPTIONSrequest in the network tab. - Streaming from the browser (
fetchwith SSE) follows the same CORS rules.
A real-life example
A startup builds a chat widget that customers embed on their own websites; it calls the startup's FastAPI backend. During a demo, the widget failed on a customer's site with a CORS error. A developer "fixed" it with allow_origins=["*"], allow_credentials=True. It worked everywhere — including, as a later security audit showed, on any site that wanted to call the dashboard API with a logged-in admin's session cookie.
The final design split the two: the public widget API uses per-customer API keys in a header, no cookies, and allows the origins each customer registered (stored in the database and checked by a custom middleware). The admin dashboard API allows exactly one origin, https://dashboard.example.com, with credentials.
Follow-up questions to expect
- "Does CORS stop curl or Postman?" — No. Only browsers enforce it. Non-browser clients ignore CORS entirely.
- "How do you allow many subdomains?" —
allow_origin_regex=r"https://.*\.example\.com", written carefully so it cannot matchexample.com.evil.net. - "Why does my preflight fail when the GET works?" — The actual request has a header or method not in
allow_headersorallow_methods, such asauthorization.