diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d939af..52ca43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,13 @@ Note that 0.3.3 was never released; 0.3.4 follows 0.3.2. are no longer read; `clear()` removes them. ([#110](https://github.com/allen0099/FastAPI-CacheX/issues/110)) +- **The session middleware reads `Authorization: bearer ` in any + letter case.** Authentication scheme names are case-insensitive (RFC 9110 + §11.1), but only the exact `Bearer ` prefix was recognised, so a client + sending `bearer` got no session. More than one space before the token is + accepted too, and a header without a token no longer yields an empty one. + ([#166](https://github.com/allen0099/FastAPI-CacheX/issues/166)) + ## [0.3.7] - 2026-09-25 ### Added diff --git a/fastapi_cachex/session/middleware.py b/fastapi_cachex/session/middleware.py index 9594b8b..bcbd4d5 100644 --- a/fastapi_cachex/session/middleware.py +++ b/fastapi_cachex/session/middleware.py @@ -111,10 +111,13 @@ def _extract_header_token( elif source == "bearer": if config.use_bearer_token: - auth_header = connection.headers.get("authorization") - if auth_header and auth_header.startswith("Bearer "): - bearer_prefix_len = 7 - token_value = auth_header[bearer_prefix_len:] + # The scheme name is case-insensitive (RFC 9110 §11.1) and is + # followed by one or more spaces (RFC 6750 §2.1). + scheme, _, token_value = connection.headers.get( + "authorization", "" + ).partition(" ") + token_value = token_value.lstrip(" ") + if scheme.lower() == "bearer" and token_value: logger.debug("Token extracted from bearer auth") return token_value diff --git a/tests/session/test_middleware.py b/tests/session/test_middleware.py index 43ef618..30efcbd 100644 --- a/tests/session/test_middleware.py +++ b/tests/session/test_middleware.py @@ -588,6 +588,38 @@ def test_bearer_is_used_when_the_header_source_finds_nothing( assert token == "from-bearer" +@pytest.mark.parametrize( + "authorization", + [ + "Bearer from-bearer", + "bearer from-bearer", + "BEARER from-bearer", + "Bearer from-bearer", + ], +) +def test_bearer_scheme_is_matched_case_insensitively( + config: SessionConfig, authorization: str +) -> None: + """Auth schemes are case-insensitive (RFC 9110 §11.1), and RFC 6750 allows + more than one space before the token (#166). + """ + token = _extract_header_token(_connection({"Authorization": authorization}), config) + + assert token == "from-bearer" + + +@pytest.mark.parametrize( + "authorization", + ["Bearer", "Bearer ", "Bearer ", "Basic from-bearer", "Bearerfrom-bearer"], +) +def test_an_empty_or_non_bearer_authorization_header_yields_no_token( + config: SessionConfig, authorization: str +) -> None: + token = _extract_header_token(_connection({"Authorization": authorization}), config) + + assert token is None + + def test_header_wins_over_bearer_when_both_are_present( config: SessionConfig, ) -> None: