Updated April 2026

Starlette CORSMiddleware: allow_origins=['*'] with allow_credentials=True

ValueError: allow_origins=['*'] is not allowed when allow_credentials=True.
Wildcards cannot be used with credentials — specify explicit origins instead.
Quick Fix

Replace allow_origins=["*"] with an explicit list: allow_origins=["https://yourdomain.com"]. Wildcards cannot be used with credentials — this is a browser security requirement, not a Starlette bug.

Why this error occurs

The CORS spec forbids Access-Control-Allow-Origin: * when a request includes credentials (cookies, Authorization headers, or TLS client certificates). Starlette enforces this at the middleware level — it raises a ValueError at startup rather than sending an invalid response that browsers would reject anyway.

This affects both FastAPI (which uses Starlette's CORSMiddleware internally) and standalone Starlette apps.

Fix 1 — Explicit origin list (recommended)

✅ Recommended

Replace the wildcard with the exact origins your frontend uses.

FastAPI

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.yourdomain.com", "https://yourdomain.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Starlette standalone

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware

middleware = [
    Middleware(
        CORSMiddleware,
        allow_origins=["https://app.yourdomain.com"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
]

app = Starlette(middleware=middleware)

Fix 2 — allow_origin_regex for dynamic origins

✅ Works with credentials

If you need to allow multiple subdomains or dynamic origins, use allow_origin_regex instead of allow_origins. It accepts a single regex pattern and works with allow_credentials=True.

app.add_middleware(
    CORSMiddleware,
    allow_origin_regex=r"https://(www\.)?yourdomain\.com",
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

For multiple domains:

# Matches yourdomain.com, app.yourdomain.com, anotherdomain.com
allow_origin_regex=r"https://(.+\.)?yourdomain\.com|https://anotherdomain\.com"

Fix 3 — Dynamic origin validation (advanced)

✅ Most flexible

For full control — validate origins against a database or config at request time:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

ALLOWED_ORIGINS = {"https://app.yourdomain.com", "https://yourdomain.com"}

app = FastAPI()

@app.middleware("http")
async def cors_middleware(request: Request, call_next):
    origin = request.headers.get("origin", "")
    response = await call_next(request)
    if origin in ALLOWED_ORIGINS:
        response.headers["Access-Control-Allow-Origin"] = origin
        response.headers["Access-Control-Allow-Credentials"] = "true"
        response.headers["Vary"] = "Origin"
    return response
Always set Vary: Origin when the Access-Control-Allow-Origin value changes per request. Without it, CDNs and proxies may cache the wrong origin value and serve it to other users.

What does allow_credentials=True actually do?

It adds Access-Control-Allow-Credentials: true to every response. This tells the browser it may include cookies, Authorization headers, and TLS client certificates in cross-origin requests. The browser will then reject any response that has Access-Control-Allow-Origin: * alongside this header — which is why Starlette prevents the combination at startup.

Do I actually need allow_credentials=True?

Only if your frontend explicitly sets credentials: "include" in fetch calls, or withCredentials: true in XMLHttpRequest. If you are just sending JSON with an Authorization header (Bearer token), you do not need allow_credentials=True — Bearer tokens are not credentials in the CORS sense. You can use allow_origins=["*"] safely with Bearer token auth.

# Bearer token auth — credentials=True NOT needed
fetch("/api/data", {
  headers: { "Authorization": "Bearer " + token }
})

# Cookie auth — credentials=True IS needed
fetch("/api/data", { credentials: "include" })

Starlette version history

This validation has been present since Starlette 0.14. The error message changed slightly in 0.20 — older versions may show a different message but the fix is identical. As of 2026 the current stable version is Starlette 0.46.x.

Test your CORS config live with CORSFixer →
📚 HttpFixer Blog — fix guides, explainers, and references →