Skip to content
Interactive demoAccountsAdvanced

Accounts and protected pages

Sign up, sign in, sessions, and ownership checks the server enforces on every mutation.

Built withAuth.js v5scryptMiddleware

Before you try it

What it needs

Nothing to configure. It runs the same everywhere.

Mode right now

ReadyThe provider is configured here; nobody has watched the real path succeed on this deployment yet.

A safe failure to try

Open this page in a private window: the protected part asks you to sign in, and the cookie panel shows there is none.

Your session

Anonymous

Read 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.

  1. 1
    The 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.

  2. 2
    Middleware 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.

  3. 3
    The 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.tsx

Registered in src/experiments/registry.ts and loaded on demand by loader.tsx.