Reference · Cookies and sessions
set_session
set_session(map | null, map | null=) -> nullWhat it does
Replaces the visitor's session with the map and sends it to the browser as a cookie
signed with HMAC-SHA256: the browser can read it but not change it. It lasts 30 days
unless the option max_age says otherwise, in seconds or a duration ("12h").
set_session(null) ends the session, as end_session does. It gives null. See
Headers, cookies and sessions.
Examples
A login: the page with the form, the route it posts to, a page that reads the session,
and a logout button. The users rows were made at signup, with
crypto.hash_password:
route GET /login = <form method="post" action="/login"> <input type="email" name="email" required/> <input type="password" name="password" required/> <button>Log in</button> </form>route POST /login { form: { email: str, password: str } } { let user = db.one("select * from users where email = ?", email) let hash = user.password_hash ?? crypto.hash_password("not anyone's") // same work, user or not if user == null || !crypto.verify_password(password, hash) { reply 401 "Wrong email or password" } set_session({ user: user.id }) redirect("/")}route GET / { let id = session().user ?? return redirect("/login") let me = db.one("select name from users where id = ?", id) <main> <p>Hello, {me.name}</p> <form method="post" action="/logout"><button>Log out</button></form> </main>}route POST /logout { set_session(null); redirect("/login") }From a component, a server fn sets it, and the answer to the call carries the cookie:
server fn sign_in(email: str, password: str) { let user = check_password(email, password) ?? return err("Wrong email or password") set_session({ user: user.id }, { max_age: "12h" }) { name: user.name }}component SignIn() { state email = "" state password = "" state problem = null <form @submit={() => { let r = sign_in(email, password) if is_err(r) { problem = r.error } else { navigate("/") } }}> <input type="email" value={email}/> <input type="password" value={password}/> <button>Sign in</button> {problem && <p class="error">{problem}</p>} </form>}print(set_session({ user: 7 }))print(session())null{ user: 7 }Edge cases
- The map replaces the session; it is not merged. To add a field, spread the old one:
set_session({ ...session(), at: now() }). max_ageis the only option. It must be more than 0 seconds;"30d"takess,m,h,d,w,y.- The signature covers the expiry, the tenant and the program's mount point, so the session is no session past its time, for another tenant or in another mounted program.
- The cookie holds the whole session as JSON, signed: keep an id in it and the rest in the database. Keep secrets out, since the browser can read it.
- Two requests of one visitor that set the session at once each start from the cookie they came with, and the last answer's cookie wins: 20 adds to a cart in the session, sent at once, kept one. Keep data that must not be lost (a cart, a draft) in a table keyed by an id in the session.
- In
parallel's functions it works as anywhere in the request, the sets made in the functions' order. - A session set in a
server fnis on the answer to that call, and the pages the visitor opens next carry it. - Outside a request the call still signs the map and
session()gives it back for the rest of the run, with a note on stderr whenQUILLO_SECRETis not set.
set_session({ user: 7, name: "Ann" })set_session({ user: 7 })print(session())set_session({ ...session(), at: 1700000000000 }, { max_age: "30d" })print(session())set_session(null)print(session()){ user: 7 }{ user: 7, at: 1700000000000 }{}Errors
A first argument that is not a map or null, a max_age that is not seconds or a
duration or is 0 or less, and options that are not a map are runtime errors, not
error values. So is a session over about 3,900 bytes once signed, more than a cookie
holds: keep an id in it and the rest in the database; and one nested deeper than 128
levels, as deep as its JSON is read back.
set_session("user 7")line 1: `set_session` expects a map, got strset_session({ user: 7 }, { max_age: 0 })line 1: `set_session`'s max_age is seconds or a duration like "30d", got 0Chaining
A sliding session is set_session called again on the requests that count as use,
once a day, from a helper every such route calls:
fn touch() { let s = session() let day = 24 * 60 * 60 * 1000 // now() is milliseconds if s.user != null && now() - (s.at ?? 0) > day { set_session({ ...s, at: now() }, { max_age: "30d" }) }}route GET /dashboard { touch(); <Dashboard me={me_or_login("/dashboard")}/> }See also
session, end_session, set_cookie, crypto.verify_password, crypto.token
Thread 0 posts
On set_session(): questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.
Edit this page on GitHub · every example's output was made by running it (tools/refcheck.mjs) · questions on Discord or r/quillo