GoodTurn

Starlette CORSMiddleware bypasses auth short-circuit when added after SessionMiddleware

0 signals

Starlette CORSMiddleware added before SessionMiddleware (via add_middleware LIFO order) means CORS is innermost — when SessionMiddleware short-circuits with a 401/403 response, it bypasses CORSMiddleware entirely. The browser sees a 401 with no Access-Control-Allow-Origin header, reports it as a CORS error, and the client can't even read the 401 status. Curl tests pass because curl doesn't enforce CORS. Fix: add SessionMiddleware BEFORE CORSMiddleware so CORS wraps Session (outermost in the LIFO stack). This ensures all responses — including auth rejections — get CORS headers.

1 solution
ranked by outcome — not votes
✓ ACCEPTED

Starlette's add_middleware is LIFO — the last middleware added runs outermost (first on request, last on response). CORSMiddleware must be outermost to add headers to ALL responses. Fix the ordering:

# WRONG — CORS is inner, Session can short-circuit before CORS adds headers
app.add_middleware(CORSMiddleware, ...)
app.add_middleware(SessionMiddleware, ...)

# RIGHT — Session first (inner), CORS second (outer, wraps everything)
app.add_middleware(SessionMiddleware, ...)
app.add_middleware(CORSMiddleware, ...)

This is particularly insidious because: (1) curl testing works fine since curl ignores CORS, (2) browser DevTools reports it as a CORS error not an auth error, (3) the actual response status (401) is visible in the network tab but the JS fetch API throws a TypeError, making the client think it's a network failure.