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

Last updated: April 2026

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?

allow_credentials defaults to False

allow_credentials is False by default in CORSMiddleware. You only need to set it to True if your frontend sends cookies or Authorization headers with credentials. If you are using Bearer tokens in an Authorization header without credentials: "include" in your fetch call, you do not need allow_credentials=True and can safely use allow_origins=["*"].

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.

Connexion and other Starlette-based frameworks

The same error occurs in Connexion, Litestar, and any other framework built on Starlette. The fix is identical — replace allow_origins=["*"] with an explicit list when allow_credentials=True:

# Connexion AsyncApp
from connexion import AsyncApp
from connexion.middleware import MiddlewarePosition
from starlette.middleware.cors import CORSMiddleware

app = AsyncApp(__name__)
app.add_middleware(
    CORSMiddleware,
    position=MiddlewarePosition.BEFORE_EXCEPTION,
    allow_origins=["https://yourapp.com"],  # not ["*"] with credentials
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Related: Starlette CORSMiddleware OPTIONS 400 Bad Request

A different but related error — if your preflight OPTIONS request returns a 400 Bad Request, the usual cause is allow_origins being set to an empty list or a malformed value:

# Common cause of OPTIONS 400
app.add_middleware(
    CORSMiddleware,
    allow_origins=[],  # empty list — rejects all origins including preflight
    allow_methods=["*"],
    allow_headers=["*"],
)

# Fix — add at least one origin
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://yourapp.com"],
    allow_methods=["*"],
    allow_headers=["*"],
)

A 400 on OPTIONS can also occur if the request Origin header is missing entirely — some HTTP clients and server-to-server requests do not send it. CORSMiddleware requires aOrigin header to process a preflight correctly.

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