Public
Magic-link auth blog with CRUD
authblog
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Magic-link authentication for Val Town, powered by Better Auth. Click Remix and you have a working auth-protected app — no setup required.
It uses Val Town SQLite for the user/session database and Val Town Email to send the magic links. Sending magic links to anyone other than yourself requires Val Town Pro.
Rendering mermaid diagram...
index.ts ← Hono entrypoint; mounts Better Auth at /api/auth/*
backend/
auth.ts ← Better Auth instance (server)
schema.ts ← Creates the user/session/account/verification tables
frontend/
index.html ← HTML shell (loads Twind + React)
index.tsx ← React entrypoint (mounts <App />)
lib/
auth-client.ts ← Better Auth React client (useSession, signIn, signOut)
components/
App.tsx ← Root: shows <SignIn /> or <Dashboard /> based on session
SignIn.tsx ← Email form that calls signIn.magicLink
Dashboard.tsx ← Signed-in view with sign-out button
- User enters their email and submits the form. The client calls
authClient.signIn.magicLink({ email }), whichPOSTs to/api/auth/sign-in/magic-link. - Better Auth generates a token, stores it in the
verificationtable, and calls oursendMagicLinkcallback inbackend/auth.ts. The callback usesstd/emailto send the link. - User clicks the link, which opens
/api/auth/magic-link/verify?token=…. Better Auth validates the token, creates a row insession, sets a secure cookie, and redirects to/. - Back in React,
useSession()re-fetches/api/auth/get-session, the user object becomes available, and<App />renders<Dashboard />.
Better Auth has a rich plugin ecosystem. Common next steps:
- Email + password — flip
emailAndPassword.enabledtotrueinbackend/auth.ts. No plugin needed. - Social login (Google, GitHub, …) — add
socialProvidersto your auth config and store the client IDs/secrets as env vars. See the social providers docs. - Passkeys, 2FA, organizations — add the relevant plugin on the server and
the matching client plugin in
frontend/lib/auth-client.ts.
After enabling new methods, you may need new database columns. See the
database concepts page — for
SQLite you'll add the columns directly to backend/schema.ts.
- Set the
BETTER_AUTH_SECRETenv var to a random string fromgenerate-random-signing-key.val.run. - Narrow
baseURL.allowedHostsinbackend/auth.tsto the exact hosts you serve from (e.g.["myapp.com", "*.web.val.run"]). - Make sure your Val Town account is on Pro so emails can be sent to addresses other than your own.
- The session cookie defaults to 7 days and refreshes itself.
- Magic-link tokens expire after 5 minutes and are single-use by default.
- Better Auth uses Kysely under the hood — Val Town's
std/sqliteis already an@libsql/clientClient, so we hand it straight toLibsqlDialect. Zero database setup.