Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog.d/377.deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
**`SessionConfig.use_bearer_token`.** `token_source_priority` already decides
whether bearer tokens are read, and 0.4.0 removes the flag. Passing it now
emits a `DeprecationWarning`, whatever its value. Replace
`use_bearer_token=False` by leaving `"bearer"` out of the list (for example
`token_source_priority=["header", "cookie"]`); `use_bearer_token=True` is the
default and can be dropped.
9 changes: 9 additions & 0 deletions changelog.d/75.deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
**Setting `token_source_priority` without `"cookie"`.** In 0.4.0 the list names
every token source. Its default becomes `["header", "bearer", "cookie"]`, the
order used today, and a list without `"cookie"` means the middleware neither
reads nor sets the session cookie. The list now accepts `"cookie"` as its last
entry, which changes nothing yet since the cookie is read there anyway; any
other position raises a `ValidationError`. `FastAPICacheXSessionMiddleware`
now emits a `FutureWarning` when the list was set explicitly without
`"cookie"`: add it as the last entry to keep the cookie. The default list does
not warn. See "Migrating to 0.4.0" in the docs.
27 changes: 25 additions & 2 deletions docs/MIGRATING_0_4.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,21 @@ Every warning below names the setting to change and links to its issue. `FutureW
| Explicit `login()` / `logout()`, read-only `Session.user` | [#256](https://github.com/allen0099/FastAPI-CacheX/issues/256) | No | [Login and logout](#login-logout) |
| `get_or_set()` locks by default | [#280](https://github.com/allen0099/FastAPI-CacheX/issues/280) | `FutureWarning` | [get_or_set lock](#get-or-set-lock) |
| `get_session_manager` resolves through `SessionManagerProxy` | [#131](https://github.com/allen0099/FastAPI-CacheX/issues/131) | `FutureWarning` | [get_session_manager](#get-session-manager) |
| `token_source_priority` names every token source; a list without `"cookie"` disables the cookie | [#75](https://github.com/allen0099/FastAPI-CacheX/issues/75) | `FutureWarning` | [Token sources](#token-source-priority) |
| `add_routes()` requires `dependencies`, no content preview by default | [#298](https://github.com/allen0099/FastAPI-CacheX/issues/298) | `UserWarning` | [Monitoring routes](#add-routes) |
| Redis `encoding` option removed | [#126](https://github.com/allen0099/FastAPI-CacheX/issues/126) | `DeprecationWarning` (`RuntimeWarning` for a value other than UTF-8) | [Redis encoding](#redis-encoding) |
| JWT HMAC secrets shorter than the hash output are rejected | [#129](https://github.com/allen0099/FastAPI-CacheX/issues/129) | `UserWarning` | [JWT secret length](#jwt-secret) |
| `SessionMiddleware` removed | [#69](https://github.com/allen0099/FastAPI-CacheX/issues/69) | `DeprecationWarning` | [SessionMiddleware](#session-middleware) |
| `BackendProxy.get_backend()` / `set_backend()` removed | [#70](https://github.com/allen0099/FastAPI-CacheX/issues/70) | `DeprecationWarning` | [BackendProxy](#backend-proxy) |
| `CacheError` removed | [#130](https://github.com/allen0099/FastAPI-CacheX/issues/130) | `DeprecationWarning` | [CacheError](#cache-error) |
| Redis `clear_pattern()` prefix-stripped retry removed | [#125](https://github.com/allen0099/FastAPI-CacheX/issues/125) | `DeprecationWarning` | [Redis clear_pattern](#redis-clear-pattern) |
| `SessionConfig.use_bearer_token` removed | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377) | `DeprecationWarning` | [Token sources](#token-source-priority) |
| `UserSessionDep` requires a user | [#127](https://github.com/allen0099/FastAPI-CacheX/issues/127) | No | [UserSessionDep](#user-session-dep) |
| `memcache` extra removed | [#202](https://github.com/allen0099/FastAPI-CacheX/issues/202) | No | [memcache extra](#memcache-extra) |
| `BaseCacheBackend.delete()` returns `bool` | [#71](https://github.com/allen0099/FastAPI-CacheX/issues/71) | No | [delete() return value](#backend-delete) |
| `CacheEntry.headers` becomes a list of pairs | [#105](https://github.com/allen0099/FastAPI-CacheX/issues/105) | No | [Repeated headers](#cache-entry-headers) |
| HTTP cache key format | [#271](https://github.com/allen0099/FastAPI-CacheX/issues/271), [#270](https://github.com/allen0099/FastAPI-CacheX/issues/270), [#269](https://github.com/allen0099/FastAPI-CacheX/issues/269), [#266](https://github.com/allen0099/FastAPI-CacheX/issues/266), [#265](https://github.com/allen0099/FastAPI-CacheX/issues/265), [#72](https://github.com/allen0099/FastAPI-CacheX/issues/72) | No | [Cache keys](#cache-keys) |
| Conditional session writes | [#128](https://github.com/allen0099/FastAPI-CacheX/issues/128) | No | [Session writes](#session-writes) |
| Cookies in `token_source_priority` | [#75](https://github.com/allen0099/FastAPI-CacheX/issues/75) | No | [Token sources](#token-source-priority) |

## Sessions {#sessions}

Expand Down Expand Up @@ -186,7 +187,29 @@ SessionConfig(

### Token sources {#token-source-priority}

`SessionConfig.token_source_priority` accepts `"cookie"` next to `"header"` and `"bearer"` in 0.4.0 ([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75)), and `FastAPICacheXSessionMiddleware` resolves the token by walking the list. Existing lists keep working; today the cookie is read after the header and bearer sources. Whether the default list gains `"cookie"`, and in which position, is not decided yet.
In 0.4.0, `SessionConfig.token_source_priority` names every token source ([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75)). `FastAPICacheXSessionMiddleware` resolves the token by walking the list, `"cookie"` may appear anywhere in it, and the default becomes `["header", "bearer", "cookie"]`, which is the order used today. A list without `"cookie"` means no cookie: the middleware neither reads nor sets it, and a session created for a request without a token sends its token in the `header_name` response header. That is how an API-only app turns the cookie off.

In 0.3.9, `"cookie"` is accepted as the last entry (where the cookie is read today, so it changes nothing yet), and `FastAPICacheXSessionMiddleware` emits a `FutureWarning` when the list was set explicitly without it. The default list does not warn.

```python
# Before: the cookie is read after these, although the list does not say so
SessionConfig(secret_key=SECRET, token_source_priority=["bearer", "header"])

# After, keeping the cookie (works on 0.3.9 and 0.4.0)
SessionConfig(secret_key=SECRET, token_source_priority=["bearer", "header", "cookie"])
```

`use_bearer_token` is removed ([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)); the list alone decides whether bearer tokens are read. 0.3.9 emits a `DeprecationWarning` when it is passed, whatever its value:

```python
# Before
SessionConfig(secret_key=SECRET, use_bearer_token=False)

# After
SessionConfig(secret_key=SECRET, token_source_priority=["header", "cookie"])
```

`use_bearer_token=True` is the default and can be dropped.

## Application cache {#application-cache}

Expand Down
41 changes: 28 additions & 13 deletions docs/SESSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,11 @@ SessionConfig(
# Token sources (API-first architecture)
token_format="simple", # "simple" (default) or "jwt"
header_name="X-Session-Token",
use_bearer_token=True,
token_source_priority=["header", "bearer"], # only these two values (see below)
use_bearer_token=True, # deprecated: leave "bearer" out of token_source_priority instead
token_source_priority=[
"header",
"bearer",
], # "cookie" only as the last entry (see below)
# JWT (used when token_format == "jwt")
jwt_algorithm="HS256", # "none" is rejected
jwt_issuer=None, # if set, iss is written and verified on parsing
Expand Down Expand Up @@ -282,17 +285,29 @@ session that many seconds after it was created, regardless of sliding renewals:
the backend TTL and a JWT's `exp` never go past `created_at + absolute_timeout`, and once
the expiry reaches that cap no further renewed tokens are issued.

#### `token_source_priority` accepts only `"header"` and `"bearer"`

The field's type is `list[Literal["header", "bearer"]]`; passing `"cookie"` is rejected by
Pydantic with a `ValidationError`. The cookie is **not** part of the priority order:
`FastAPICacheXSessionMiddleware` resolves tokens in a fixed order — it first reads
header/bearer following `token_source_priority`, and only falls back to the cookie when neither
yields a token. This is deliberate: the response side is routed by the token's source (header in,
header out; cookie in, `Set-Cookie` out), and mixing the cookie into the same priority list would
make a `["cookie"]` setting fail silently on the deprecated `SessionMiddleware`. Once
`SessionMiddleware` is removed in 0.4.0, all three sources may be described by a single priority
list.
#### `token_source_priority` and the session cookie

`FastAPICacheXSessionMiddleware` first reads the header sources in `token_source_priority` order,
and only falls back to the cookie when none of them yields a token. The response side follows the
token's source (header in, header out; cookie in, `Set-Cookie` out).

Until 0.4.0 the cookie is read whether or not the list names it. `"cookie"` is accepted only as the
last entry, which is where it is read anyway, so listing it changes nothing yet; any other position
raises a `ValidationError`. The deprecated `SessionMiddleware` never reads the cookie and ignores
the entry.

In 0.4.0 the list names every token source, and its default becomes `["header", "bearer",
"cookie"]`, the order used today. A list without `"cookie"` then means no cookie at all: the
middleware neither reads nor sets it, and a session created for a request without a token sends
its token in the `header_name` response header ([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75)).
So `FastAPICacheXSessionMiddleware` emits a `FutureWarning` when the list was set explicitly
without `"cookie"`. Add `"cookie"` as the last entry to keep the cookie; the default list does not
warn. See [Migrating to 0.4.0](MIGRATING_0_4.md#token-source-priority).

`use_bearer_token` is deprecated and removed in 0.4.0 ([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)):
passing it emits a `DeprecationWarning`. Instead of `use_bearer_token=False`, leave `"bearer"` out
of the list (`token_source_priority=["header", "cookie"]`); `use_bearer_token=True` is the
default and can simply be dropped.

**Header/bearer clients** should store the token in `localStorage` or `sessionStorage` and send
it as `Authorization: Bearer <token>` or `X-Session-Token: <token>`. **Cookie clients** (browsers)
Expand Down
50 changes: 47 additions & 3 deletions fastapi_cachex/session/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,14 @@ class SessionConfig(BaseModel):
)
use_bearer_token: bool = Field(
default=True,
description="Whether to accept Authorization Bearer tokens",
description="Whether to accept Authorization Bearer tokens (deprecated: "
'leave "bearer" out of token_source_priority instead; removed in 0.4.0)',
)
token_source_priority: list[Literal["header", "bearer"]] = Field(
token_source_priority: list[Literal["header", "bearer", "cookie"]] = Field(
default=["header", "bearer"],
description="Priority order for token sources",
description="Priority order for token sources. The session cookie is "
'read after the header sources; "cookie" may only be the last entry '
"until 0.4.0, which drops the cookie from a list without it",
)

# JWT settings (used when token_format == 'jwt')
Expand Down Expand Up @@ -276,6 +279,47 @@ def _warn_invalid_cookie_prefix(self) -> "SessionConfig":
)
return self

@field_validator("token_source_priority")
@classmethod
def _check_cookie_is_last(
cls, value: list[Literal["header", "bearer", "cookie"]]
) -> list[Literal["header", "bearer", "cookie"]]:
"""Only accept ``"cookie"`` where the cookie is read today: last.

``FastAPICacheXSessionMiddleware`` reads the cookie after the header
sources, so listing it last changes nothing before 0.4.0, when the
list decides the order and a list without it disables the cookie (#75).
"""
if "cookie" in value and value.index("cookie") != len(value) - 1:
msg = (
'"cookie" must be the last entry of token_source_priority: the '
"session cookie is read after the header sources until 0.4.0 "
"(https://github.com/allen0099/FastAPI-CacheX/issues/75)"
)
raise ValueError(msg)
return value

@model_validator(mode="after")
def _warn_use_bearer_token(self) -> "SessionConfig":
"""Deprecate ``use_bearer_token``, which 0.4.0 removes (#377).

``token_source_priority`` already decides whether bearer tokens are
read, so passing the flag at all warns, whatever its value.
"""
if "use_bearer_token" in self.model_fields_set:
warnings.warn(
"SessionConfig(use_bearer_token=...) is deprecated and will be "
"removed in version 0.4.0. token_source_priority decides the "
"token sources: drop use_bearer_token=True (the default), and "
'replace use_bearer_token=False by leaving "bearer" out of '
'token_source_priority, keeping "cookie" last, e.g. '
'["header", "cookie"] '
"(https://github.com/allen0099/FastAPI-CacheX/issues/377).",
DeprecationWarning,
stacklevel=3,
)
return self

@field_validator("jwt_algorithm")
@classmethod
def _check_jwt_algorithm(cls, value: str) -> str:
Expand Down
42 changes: 38 additions & 4 deletions fastapi_cachex/session/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ def _read_header_token(
"""
consulted: list[str] = []
# `token_source_priority` is a list of Literals, so pydantic has already
# rejected anything that is neither branch; the chain stays an `elif` so a
# source added later falls through instead of being read as a bearer token.
# rejected anything else; the chain stays an `elif` so "cookie" (read by
# FastAPICacheXSessionMiddleware after these) and any source added later
# fall through instead of being read as a bearer token.
for source in config.token_source_priority:
if source == "header":
consulted.append(config.header_name)
Expand Down Expand Up @@ -218,13 +219,44 @@ def _warn_if_default_cookie(config: SessionConfig) -> None:
)


def _warn_if_priority_without_cookie(config: SessionConfig) -> None:
"""Warn that 0.4.0 stops using the cookie for a list without ``"cookie"``.

In 0.4.0 ``token_source_priority`` lists every token source, so a list
set without ``"cookie"`` means the middleware neither reads nor sets the
session cookie (#75). The default list becomes ``["header", "bearer",
"cookie"]``, which is today's order, so it does not warn.

Args:
config: The configuration the middleware uses
"""
if (
"token_source_priority" not in config.model_fields_set
or "cookie" in config.token_source_priority
):
return
warnings.warn(
f"SessionConfig(token_source_priority={config.token_source_priority!r}) "
'does not list "cookie". In version 0.4.0 the list names every token '
"source, so FastAPICacheXSessionMiddleware will neither read nor set the "
"session cookie, and a session created for a request without a token "
'sends its token in the header_name response header. Add "cookie" as the '
"last entry to keep the cookie; leaving it out opts into 0.4.0's "
"cookie-less behaviour, which only takes effect then "
"(https://github.com/allen0099/FastAPI-CacheX/issues/75).",
FutureWarning,
stacklevel=3,
)


class SessionMiddleware(BaseHTTPMiddleware):
"""Middleware to handle session loading and token extraction.

Extracts the session token from the request (via a custom header and/or
an ``Authorization: Bearer`` header, per ``SessionConfig.token_source_priority``)
and loads the corresponding session into ``request.state``. Cookie-based
token transport is not currently supported.
token transport is not supported, so a ``"cookie"`` entry in the list is
ignored.

.. deprecated:: 0.3.1
Use :class:`FastAPICacheXSessionMiddleware` instead. Will be removed in
Expand Down Expand Up @@ -413,12 +445,14 @@ def __init__(
Warns:
FutureWarning: If the configuration leaves ``cookie_name`` or
``cookie_https_only`` at its default, both of which change in
0.4.0.
0.4.0, or sets ``token_source_priority`` without ``"cookie"``,
which disables the cookie in 0.4.0.
"""
self.app = app
self.session_manager = session_manager or SessionManagerProxy.get()
self.config = config or self.session_manager.config
_warn_if_default_cookie(self.config)
_warn_if_priority_without_cookie(self.config)

security_flags = f"httponly; samesite={self.config.cookie_same_site}"
if self.config.cookie_https_only:
Expand Down
Loading
Loading