117 lines
4.3 KiB
Markdown
117 lines
4.3 KiB
Markdown
# kaya-session
|
|
|
|
Session management for the Kaya web framework.
|
|
|
|
Provides server-side, identity-agnostic HTTP sessions via a session cookie. The
|
|
session data is accessible from request handlers as `ctx.session`.
|
|
|
|
## Usage
|
|
|
|
```python
|
|
from kaya.core import KayaApp, HttpContext
|
|
from kaya.session import SessionMixin, InMemorySessionStore
|
|
|
|
session = SessionMixin(InMemorySessionStore())
|
|
app = KayaApp(mixins=[session])
|
|
|
|
@app.GET('/')
|
|
async def home(ctx: HttpContext):
|
|
n = ctx.session.get('visits', 0) + 1
|
|
ctx.session['visits'] = n
|
|
await ctx.send_str(200, f'visits: {n}')
|
|
```
|
|
|
|
Sessions are created lazily: a cookie is only set when the handler modifies the
|
|
session.
|
|
|
|
`SessionMixin` is a `KayaMixin`, so the app stays a `KayaApp` and both ASGI and
|
|
RSGI keep working.
|
|
|
|
## WebSocket sessions
|
|
|
|
The same session is available in websocket handlers as `ws.session`:
|
|
|
|
```python
|
|
@app.websocket('/ws/visits')
|
|
async def ws_visits(ws: WebSocket):
|
|
await ws.accept()
|
|
visits = ws.session.get('visits', 0)
|
|
await ws.send_text(f'visits: {visits}')
|
|
```
|
|
|
|
The session is loaded from the cookie when the connection is opened and
|
|
persisted when the connection closes, if it was modified.
|
|
|
|
**Important:** although the ASGI spec allows custom headers on the WebSocket
|
|
handshake response (`websocket.accept` headers, spec 2.1+), most ASGI servers
|
|
in practice — including **Granian** and **Daphne** — do not forward them into
|
|
the HTTP `101` response. RSGI websocket handshakes cannot carry response headers
|
|
at all. Therefore, in real deployments a session cookie can only be set or
|
|
refreshed by an HTTP response. Use an HTTP endpoint to establish or update the
|
|
session before opening the WebSocket, and read the existing session in the
|
|
WebSocket handler.
|
|
|
|
## Session expiry
|
|
|
|
The cookie sent to the browser has a `Max-Age` (default 14 days), but that is
|
|
only a client-side hint. The real boundary is the store's server-side TTL,
|
|
which the mixin keeps in sync with the cookie `Max-Age`.
|
|
|
|
For `InMemorySessionStore`, a session expires if it is idle for longer than
|
|
`max_age`. Active sessions have their expiry slid forward on every access, so
|
|
a user that keeps visiting stays logged in. If the client ignores the cookie's
|
|
`Max-Age` and replays an old cookie value, the store rejects the expired
|
|
session and creates a fresh empty one.
|
|
|
|
Set `max_age=None` to disable server-side expiry (and the `Max-Age` cookie
|
|
attribute) entirely.
|
|
|
|
## Session indexes
|
|
|
|
Authentication layers can attach reverse-index metadata to a session without
|
|
adding it to the user-visible session dictionary:
|
|
|
|
```python
|
|
ctx.session.add_index('oidc:default:sub', 'user-123')
|
|
```
|
|
|
|
Index-capable stores persist these memberships on `save()` and maintain them
|
|
across session ID rotation and deletion:
|
|
|
|
```python
|
|
session_ids = await store.find_sessions('oidc:default:sub', 'user-123')
|
|
deleted = await store.delete_by_index('oidc:default:sub', 'user-123')
|
|
```
|
|
|
|
Use `session.remove_index(name, value)` to remove a membership while keeping
|
|
the session. `SessionMixin.store` exposes the backing store for features such
|
|
as back-channel logout.
|
|
|
|
`InMemorySessionStore`, `RedisSessionStore`, and `MemcacheSessionStore` set
|
|
`supports_indexes = True`. Custom stores can opt in by setting that flag,
|
|
implementing `find_sessions()`, maintaining reverse memberships on `save()`,
|
|
and removing them on `delete()`. `delete_by_index()` has a generic default
|
|
implementation based on `find_sessions()` and `delete()`.
|
|
|
|
Remote stores serialize an envelope containing both the session dictionary and
|
|
its index metadata. Existing plain-dict payloads remain readable and are
|
|
upgraded on the next save.
|
|
|
|
## Features
|
|
|
|
- `Session`: dict-like session object with modification tracking
|
|
- `SessionStore`: abstract store interface
|
|
- `InMemorySessionStore`: simple in-memory store for development/single-process
|
|
- `SessionMixin`: composable Kaya mixin managing session cookies and persistence
|
|
- Session ID regeneration (`session.regenerate_id()`) and invalidation
|
|
(`session.invalidate()`) for authentication layers
|
|
- Reverse session indexes for looking up and invalidating sessions by metadata
|
|
- WebSocket support: the session is exposed as `ws.session` in websocket
|
|
handlers, loaded at connect time and persisted on close
|
|
|
|
## Notes
|
|
|
|
- `InMemorySessionStore` does not survive process restarts and is not shared
|
|
across processes. Production deployments should use a store backed by a shared
|
|
storage system (planned).
|