From 39c87b0a2f8f67e8cb05e681d2e8a37f5878de01 Mon Sep 17 00:00:00 2001 From: westinjiang Date: Sun, 4 Jan 2026 19:59:46 -0500 Subject: [PATCH] Fix HTTPBasic to always include realm in WWW-Authenticate --- fastapi/security/http.py | 5 ++--- .../test_security_http_basic_default_realm.py | 20 +++++++++++++++++++ tests/test_security_http_basic_optional.py | 9 +++++++-- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 tests/test_security_http_basic_default_realm.py diff --git a/fastapi/security/http.py b/fastapi/security/http.py index b4c3bc6d8..518ce23fa 100644 --- a/fastapi/security/http.py +++ b/fastapi/security/http.py @@ -197,9 +197,8 @@ class HTTPBasic(HTTPBase): self.auto_error = auto_error def make_authenticate_headers(self) -> dict[str, str]: - if self.realm: - return {"WWW-Authenticate": f'Basic realm="{self.realm}"'} - return {"WWW-Authenticate": "Basic"} + realm = self.realm or "FastAPI" + return {"WWW-Authenticate": f'Basic realm="{realm}"'} async def __call__( # type: ignore self, request: Request diff --git a/tests/test_security_http_basic_default_realm.py b/tests/test_security_http_basic_default_realm.py new file mode 100644 index 000000000..21092e7b2 --- /dev/null +++ b/tests/test_security_http_basic_default_realm.py @@ -0,0 +1,20 @@ +from fastapi import Depends, FastAPI +from fastapi.security import HTTPBasic, HTTPBasicCredentials +from starlette.testclient import TestClient + + +def test_http_basic_includes_realm_by_default(): + app = FastAPI() + security = HTTPBasic() # no realm provided + + @app.get("/protected") + def protected(credentials: HTTPBasicCredentials = Depends(security)): + return {"username": credentials.username} + + client = TestClient(app) + resp = client.get("/protected") + + assert resp.status_code == 401 + www_auth = resp.headers.get("www-authenticate") + assert www_auth is not None + assert www_auth.startswith('Basic realm="') diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py index 7071f381a..70caa416a 100644 --- a/tests/test_security_http_basic_optional.py +++ b/tests/test_security_http_basic_optional.py @@ -36,8 +36,10 @@ def test_security_http_basic_invalid_credentials(): response = client.get( "/users/me", headers={"Authorization": "Basic notabase64token"} ) + www_auth = response.headers["WWW-Authenticate"] assert response.status_code == 401, response.text - assert response.headers["WWW-Authenticate"] == "Basic" + assert www_auth.lower().startswith("basic") + assert 'realm="' in www_auth.lower() assert response.json() == {"detail": "Not authenticated"} @@ -45,8 +47,11 @@ def test_security_http_basic_non_basic_credentials(): payload = b64encode(b"johnsecret").decode("ascii") auth_header = f"Basic {payload}" response = client.get("/users/me", headers={"Authorization": auth_header}) + www_auth = response.headers["WWW-Authenticate"] assert response.status_code == 401, response.text - assert response.headers["WWW-Authenticate"] == "Basic" + assert www_auth.lower().startswith("basic") + assert 'realm="' in www_auth.lower() + assert response.json() == {"detail": "Not authenticated"}