From 04c09e522025348ec79e7b850032f0ed9869d325 Mon Sep 17 00:00:00 2001 From: jsibitoye Date: Tue, 23 Sep 2025 12:09:13 -0500 Subject: [PATCH] Fix: Add realm to Basic Auth WWW-Authenticate header (RFC 7617 compliance) --- fastapi/security/http.py | 8 ++--- tests/test_httpbasic_realm.py | 29 +++++++++++++++++++ tests/test_security_http_basic_optional.py | 4 +-- .../test_security/test_tutorial006.py | 6 ++-- 4 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 tests/test_httpbasic_realm.py diff --git a/fastapi/security/http.py b/fastapi/security/http.py index 9ab2df3c9..434976f43 100644 --- a/fastapi/security/http.py +++ b/fastapi/security/http.py @@ -189,10 +189,10 @@ class HTTPBasic(HTTPBase): ) -> Optional[HTTPBasicCredentials]: authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) - if self.realm: - unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{self.realm}"'} - else: - unauthorized_headers = {"WWW-Authenticate": "Basic"} + # Always include a realm as required by RFC 7617. + realm_value = self.realm or "fastapi" + unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{realm_value}"'} + if not authorization or scheme.lower() != "basic": if self.auto_error: raise HTTPException( diff --git a/tests/test_httpbasic_realm.py b/tests/test_httpbasic_realm.py new file mode 100644 index 000000000..b9fc5e556 --- /dev/null +++ b/tests/test_httpbasic_realm.py @@ -0,0 +1,29 @@ +from fastapi import Depends, FastAPI +from fastapi.security import HTTPBasic +from fastapi.testclient import TestClient + +def test_default_realm_is_included(): + app = FastAPI() + security = HTTPBasic() + + @app.get("/protected") + def protected(_: str = Depends(security)): + return {"ok": True} + + client = TestClient(app) + response = client.get("/protected") + assert response.status_code == 401 + assert response.headers["www-authenticate"] == 'Basic realm="fastapi"' + +def test_custom_realm_is_respected(): + app = FastAPI() + security = HTTPBasic(realm="custom") + + @app.get("/protected") + def protected(_: str = Depends(security)): + return {"ok": True} + + client = TestClient(app) + response = client.get("/protected") + assert response.status_code == 401 + assert response.headers["www-authenticate"] == 'Basic realm="custom"' diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py index 9b6cb6c45..54aaef070 100644 --- a/tests/test_security_http_basic_optional.py +++ b/tests/test_security_http_basic_optional.py @@ -37,7 +37,7 @@ def test_security_http_basic_invalid_credentials(): "/users/me", headers={"Authorization": "Basic notabase64token"} ) assert response.status_code == 401, response.text - assert response.headers["WWW-Authenticate"] == "Basic" + assert response.headers["WWW-Authenticate"] == 'Basic realm="fastapi"' assert response.json() == {"detail": "Invalid authentication credentials"} @@ -46,7 +46,7 @@ def test_security_http_basic_non_basic_credentials(): auth_header = f"Basic {payload}" response = client.get("/users/me", headers={"Authorization": auth_header}) assert response.status_code == 401, response.text - assert response.headers["WWW-Authenticate"] == "Basic" + assert response.headers["WWW-Authenticate"] == 'Basic realm="fastapi"' assert response.json() == {"detail": "Invalid authentication credentials"} diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py index 40b413806..b2944fff8 100644 --- a/tests/test_tutorial/test_security/test_tutorial006.py +++ b/tests/test_tutorial/test_security/test_tutorial006.py @@ -32,7 +32,7 @@ def test_security_http_basic_no_credentials(client: TestClient): response = client.get("/users/me") assert response.json() == {"detail": "Not authenticated"} assert response.status_code == 401, response.text - assert response.headers["WWW-Authenticate"] == "Basic" + assert response.headers["WWW-Authenticate"] == 'Basic realm="fastapi"' def test_security_http_basic_invalid_credentials(client: TestClient): @@ -40,7 +40,7 @@ def test_security_http_basic_invalid_credentials(client: TestClient): "/users/me", headers={"Authorization": "Basic notabase64token"} ) assert response.status_code == 401, response.text - assert response.headers["WWW-Authenticate"] == "Basic" + assert response.headers["WWW-Authenticate"] == 'Basic realm="fastapi"' assert response.json() == {"detail": "Invalid authentication credentials"} @@ -49,7 +49,7 @@ def test_security_http_basic_non_basic_credentials(client: TestClient): auth_header = f"Basic {payload}" response = client.get("/users/me", headers={"Authorization": auth_header}) assert response.status_code == 401, response.text - assert response.headers["WWW-Authenticate"] == "Basic" + assert response.headers["WWW-Authenticate"] == 'Basic realm="fastapi"' assert response.json() == {"detail": "Invalid authentication credentials"}