Unit 10.03: CORS, and what it actually protects
CORS is enforced by the browser, for browsers, and protects nothing else.
What it does and what it does not
An app with an allowed origin, called from an allowed and a disallowed one.
The code shows the header returned in each case.
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"],
allow_methods=["GET", "POST"], allow_headers=["*"])
@app.get("/data")
def data() -> dict:
return {"ok": True}
client = TestClient(app)
for origin in ("https://app.example.com", "https://evil.example.com"):
r = client.get("/data", headers={"Origin": origin})
allowed = r.headers.get("access-control-allow-origin")
print(f"{origin:30} -> allow-origin: {allowed}")
print("""
CORS is enforced by the BROWSER, for browser-initiated cross-origin requests.
It stops a page on another origin reading your response.
It stops nothing else. curl, a script and a server ignore it entirely, so CORS
is not an access control -- the API key and the scope check are.
""")
The mechanism is a header telling the *browser* whether a page on another origin may read the response. The request still reached your server in both cases.
curl, a script, a mobile app and another server ignore CORS entirely. So it is not an access control - the API key and the scope check from the previous unit are.
The mistake this prevents
The mistake is setting a permissive CORS policy and considering the API protected, or setting a restrictive one and considering it secured. Neither is true: CORS governs browser reads and nothing else.
Takeaway
CORS controls what a browser lets a page read cross-origin. Every non-browser client ignores it, so it is not an access control.
