Storage¶
The server-side store for sessions, CSRF tokens, and one-time tokens. Pick a backend with
get_session_storage(...), or implement AbstractSessionStorage for your own.
crudauth.storage.AbstractSessionStorage
¶
AbstractSessionStorage(
prefix: str = DEFAULT_STORAGE_PREFIX,
expiration: int = DEFAULT_SESSION_TTL_SECONDS,
)
Bases: ABC, Generic[T]
Async key/value store for serializable Pydantic models with TTLs.
Concrete backends serialize T to JSON, key it under {prefix}{id} and
honor per-key expiration.
Optional capabilities (duck-typed - implement if your backend can, callers
check with hasattr):
async get_user_sessions(user_id) -> list[str]- index sessions by user; unlocks multi-device limits and "sign out everywhere".async scan_keys(match: str | None = None) -> list[str]- enumerate keys by glob; unlocks the periodic idle-session cleanup sweep. A backend without it simply gets no proactive sweep (per-key TTLs still expire entries).
Example
create
abstractmethod
async
¶
Store data under session_id (generated if omitted) with a TTL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
T
|
The Pydantic model to serialize. |
required |
session_id
|
str | None
|
Key to store under; a fresh UUID if |
None
|
expiration
|
int | None
|
TTL in seconds; the storage default if |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The session id the value was stored under. |
get
abstractmethod
async
¶
Load and deserialize a value into model_class, or None if absent/expired.
update
abstractmethod
async
¶
update(
session_id: str,
data: T,
reset_expiration: bool = True,
expiration: int | None = None,
) -> bool
Overwrite an existing value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
Key to update. |
required |
data
|
T
|
New value to serialize. |
required |
reset_expiration
|
bool
|
If |
True
|
expiration
|
int | None
|
TTL in seconds when resetting; storage default if |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
|
delete
abstractmethod
async
¶
Delete a session. user_id, when known by the caller, lets indexed
backends skip re-reading the record to update their per-user index.
extend
abstractmethod
async
¶
Extend a key's TTL.
Note
expiration=None falls back to the storage default
([expiration][crudauth.storage.base.AbstractSessionStorage.expiration]), NOT the caller's session window - callers that
slide a specific window (e.g. the CSRF-token TTL) must pass an
explicit expiration.
set_if_absent
async
¶
Atomically store data under session_id only if it is absent.
Returns True if this call created the entry (the caller "won"),
False if it already existed. This is the primitive behind a correct
one-time-use token guard: a plain exists then create is a
check-then-set race on a networked backend.
Note
This default implementation is only atomic on a backend whose
exists/create never yield to the event loop (the in-memory
backend). Networked backends MUST override it with a native atomic
operation (Redis SET NX); see
RedisSessionStorage.
get_and_delete
async
¶
Atomically read and delete an entry, returning it (or None).
Lets a single-use value (e.g. OAuth state) be consumed exactly once
even under concurrent callbacks. As with set_if_absent, the default
is only atomic on the in-memory backend; networked backends override it.
get_user_sessions
async
¶
Optional: session ids belonging to user_id.
Implement when the backend can index by user - unlocks multi-device
limits and "sign out everywhere". Meaningful only for user_id-bearing
models. Raises NotImplementedError when unsupported.
scan_keys
async
¶
Optional: enumerate stored keys by glob.
Unlocks the periodic idle-session cleanup sweep. A backend without it
gets no proactive sweep (per-key TTLs still expire entries). Raises
NotImplementedError when unsupported.
delete_pattern
async
¶
Optional: delete keys by prefix. Raises NotImplementedError
when unsupported. Never point this at the login:* lockout keys -
bulk-deleting them would clear an attacker's accumulated failures.
crudauth.storage.MemorySessionStorage
¶
MemorySessionStorage(
prefix: str = DEFAULT_STORAGE_PREFIX,
expiration: int = DEFAULT_SESSION_TTL_SECONDS,
)
Bases: AbstractSessionStorage[T]
Dict-backed storage. Not shared across processes - never use in production.
Note
Eviction is lazy (on access to a key) plus an occasional full sweep on write, so abandoned keys don't accumulate unboundedly the way a purely-lazy dict would. It is still single-process and unsuitable for production - use the redis backend there.
delete
async
¶
Delete a session. user_id is accepted for the shared contract but
unused here (this backend keeps no separate per-user index).
scan_keys
async
¶
Enumerate keys by glob (e.g. "session:*"), matching the Redis
backend's semantics. None returns all keys.
get_user_sessions
async
¶
Scan all sessions and return ids belonging to user_id.
Note
Reads the user_id field straight out of the serialized payload
(the storage layer is model-agnostic, so it can't go through the
model). The comparison is on the stringified id, matching how the
Redis backend keys its per-user index - so a UUID PK (which a JSON
round-trip turns into a string) still matches the UUID object the
caller passes. Meaningful only for user_id-bearing models;
entries without that field are skipped.
delete_pattern
async
¶
Delete keys whose name starts with pattern (matches {pattern}*),
the same prefix semantics as the Redis backend.
crudauth.storage.RedisSessionStorage
¶
RedisSessionStorage(
prefix: str = DEFAULT_STORAGE_PREFIX,
expiration: int = DEFAULT_SESSION_TTL_SECONDS,
redis_url: str | None = None,
client: Any = None,
**_: Any,
)
Bases: AbstractSessionStorage[T]
Async Redis backend with a per-user session index for fast enumeration.
Layout
{prefix}{session_id}-> serialized model (TTL = expiration){prefix_root}_users:{user_id}-> SET of session ids (TTL = expiration + 1h)
Note
Pass an existing client= to share one connection pool with other
redis-backed components (e.g. the rate-limiter backend); otherwise each
constructs its own pool to the same server.
delete
async
¶
Delete a session and drop it from its owner's index.
Note
When user_id is given (the indexed terminate paths know it), the
owner read is skipped entirely. When it's None (e.g. logout with
only a cookie), the record is read once to find the owner so the
user index stays consistent. The index assumes a user_id-bearing
model; for other models nothing is read or indexed.
set_if_absent
async
¶
Atomic create-if-absent via SET key value NX EX ttl (single round trip).
crudauth.storage.get_session_storage
¶
get_session_storage(
backend: str = BACKEND_MEMORY,
*,
prefix: str = DEFAULT_STORAGE_PREFIX,
expiration: int = DEFAULT_SESSION_TTL_SECONDS,
redis_url: str | None = None,
**kwargs: Any,
) -> AbstractSessionStorage[Any]
Construct a storage backend by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
backend
|
str
|
|
BACKEND_MEMORY
|
prefix
|
str
|
Key namespace prefix. |
DEFAULT_STORAGE_PREFIX
|
expiration
|
int
|
Default TTL in seconds. |
DEFAULT_SESSION_TTL_SECONDS
|
redis_url
|
str | None
|
Connection URL, required for |
None
|
Returns:
| Type | Description |
|---|---|
AbstractSessionStorage[Any]
|
An AbstractSessionStorage for the requested backend. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |