Accounts and protected pages
Sign up, sign in, sessions, and ownership checks the server enforces on every mutation.
Before you try it
What it needs
Mode right now
A safe failure to try
Your session
AnonymousRead on the server from the session cookie, then rendered into this HTML.
No session cookie was sent with this request, so the server rendered the anonymous version of this page.
The cookie
A JWT session: the token carries the user id and role, so no database lookup is needed to know who is calling.
- Present
- no
- httpOnly
- yes — JavaScript cannot read it
- SameSite
- lax — not sent on cross-site POSTs
- Secure
- in production only (HTTPS)
- Contents
- signed and encrypted (JWE)
Open the browser console and run document.cookie. The session token will not be there — that is httpOnly doing its job. A token kept in localStorage would be readable by any script on the page, which is how a single XSS bug turns into stolen sessions.
Where authorization actually happens
Three layers. Only the last two are security; the first is convenience.
- 1The UI hides what you cannot docosmetic
Edit and delete buttons only render on projects you own. This is a courtesy — anyone can call the endpoint directly, so it protects nothing.
- 2Middleware redirects protected routespage-level
/settings, /admin redirect to /sign-in without a session. This protects pages, not data, and it runs on the edge — which is why it uses a database-free config.
- 3The Server Action checks ownershipsecurity boundary
Every mutation re-reads the row and compares its ownerId to the session. This is the boundary that actually holds, because it cannot be skipped by calling the endpoint directly.
Try it: sign in as one user, create a project, then sign out and try to edit it from the CRUD experiment. The buttons disappear — and the action would refuse even if you called it directly.
What this demonstrates
- Passwords stored as a salted scrypt hash, never as the original
- httpOnly cookies so an XSS bug cannot steal the session
- Equal work for unknown and known emails, so timing reveals nothing
- Authorization enforced in the Server Action, not by hiding a button
- An attacker-supplied callbackUrl is a phishing vector unless validated
Where the code lives
src/experiments/auth/demo.tsxRegistered in src/experiments/registry.ts and loaded on demand by loader.tsx.