Sessions¶
A session is a dict in a signed cookie.
import os
from aether import App, Depends, Request, Sessions
sessions = Sessions(secret=os.environ["SECRET_KEY"])
app = App()
app.middleware(sessions.middleware)
@app.get("/count")
async def count(_: Request, session = Depends(sessions.load)):
session["views"] = session.get("views", 0) + 1
return {"views": session["views"]}
Two halves, and both are needed: the dependency reads the cookie and hands the
handler a Session, and the middleware writes it back out.
The cookie is rewritten only when the handler actually changed the session, so
a read-only request sends no Set-Cookie at all. Session is a dict that
knows whether it was modified; clear, pop and update count as
modifications.
Signed, not encrypted¶
Warning
The client cannot forge or edit a session, but it can read it. The payload is base64, not ciphertext.
Put an identifier in a session and look the rest up. Never put a password, a token, or anything you would not show the person holding the cookie.
Signing is HMAC-SHA256, compared in constant time. A tampered or expired cookie
is treated as no session at all rather than as an error, because a client with
a stale cookie should get a fresh session, not a 400.
Options¶
Sessions(
secret=os.environ["SECRET_KEY"],
cookie="aether_session",
max_age=1209600, # two weeks
secure=True, # HTTPS only
same_site="Lax",
path="/",
)
The cookie is always HttpOnly, so page scripts cannot read it.
secure=True is the default, which means the cookie is not sent over plain
HTTP. Turn it off for local development over http://localhost, and turn it
back on for anything else.
Rotating the secret¶
Changing the secret invalidates every existing session; each client simply gets a new one. There is no multi-key rotation window yet.