codexmachina
Auth provider

Every verified stack secured with Better Auth.

Better Auth: self-hosted auth running inside your app against your Postgres (Drizzle adapter).

240 verified stacks240 greenLast updated 2026-08-23How we verify →

240 verified stacks

Showing 12 across 12 app types. Every stack page links its neighbours along each axis, so any combination is a click or two from here.

AI WrapperNext.js 16 (App Router) · Postgres (Neon)
Blog / CMSNext.js 16 (App Router) · MySQL 8
Booking / schedulingNext.js 16 (App Router) · MySQL 8
CRMNext.js 16 (App Router) · MySQL 8
E-commerce storeNext.js 16 (App Router) · MySQL 8
Fintech ledgerNext.js 16 (App Router) · MySQL 8
Fitness trackerNext.js 16 (App Router) · MySQL 8
Forum / communityNext.js 16 (App Router) · MySQL 8 · Resend
Helpdesk / supportNext.js 16 (App Router) · MySQL 8 · Resend
IoT telemetryNuxt 4 · MySQL 8
Job boardNext.js 16 (App Router) · Postgres (Neon)
LMS (learning platform)Next.js 16 (App Router) · MySQL 8

What Better Auth gives you

Better Auth is a TypeScript library, not a service. The process that serves your pages is the process that hashes passwords and issues sessions, and the session rows sit in the same database as your application data. This fragment authors the four tables Better Auth expects — user, session, account, verification — into db/auth-schema.ts and passes that module to drizzleAdapter(db, { provider, schema: authSchema }) inside lib/auth.ts. Better Auth never creates tables at runtime; its CLI normally generates them, and authoring them here means the identity schema goes through the same migration proof as the app-type tables.

user.id is a bare text primary key (varchar(255) on MySQL, which cannot index TEXT without a prefix length) carrying no database default, because Better Auth generates the id and sends it in the insert. That is precisely what lets an app-type schema foreign-key user.id and cascade on delete. Session validation happens at two different strengths, deliberately. Next's src/proxy.ts calls getSessionCookie(request), which only asks whether the cookie is present: it runs at the edge, touches no database, and exists to bounce logged-out traffic before render. The authoritative check is auth.api.getSession({ headers }), and it runs inside the protected surface — requireUser() in src/lib/session.ts on Next, requireAuth(request) in app/lib/require-auth.ts on React Router, and the Nitro handler at server/middleware/auth.ts on Nuxt.

The last two do the real lookup on every guarded request, since neither framework has an edge proxy to peek with. The remaining emitted files are the mount: toNextJsHandler(auth) behind a [...all] route on Next, a resource route delegating to auth.handler on React Router, an h3 catch-all wrapping toWebRequest on Nuxt, plus a Vue auth client there. The auth instance imports the db client the framework already exported, so both share one pooled connection. What you inherit is ownership. Sessions join to your own tables, a user delete is a foreign-key cascade rather than a sync job, and drift arrives through your lockfile instead of a vendor's release notes.

The same ownership is the cost: password recovery only delivers if an email fragment is composed in — Better Auth's own server returns 400 "Reset password isn't enabled" until emailAndPassword.sendResetPassword is set — and rotating BETTER_AUTH_SECRET is yours to schedule.

Built for

Gate a server component on a real session

requireUser() in src/lib/session.ts awaits auth.api.getSession({ headers: await headers() }) and redirects to /sign-in. The getSessionCookie check in src/proxy.ts is a cookie-presence peek only — it never proves the session is still valid, so the page-level call is the one that counts.

Foreign-key application tables to the signed-in user

db/auth-schema.ts declares user.id as a text (varchar(255) on MySQL) primary key with no DB default, and session/account reference it with onDelete: "cascade". App-type schemas point their user_id columns at that same table.

Sign someone up with email and password

The vendored sign-up screen calls authClient.signUp.email({ name, email, password }) through createAuthClient() in src/lib/auth-client.ts; the request lands on the [...all] route that toNextJsHandler(auth) mounts, and Better Auth writes the user + account rows itself.

Deliver verification and password-reset mail

Composing an email fragment fills emailAndPassword.sendResetPassword and emailVerification.sendVerificationEmail in src/lib/auth.ts with the imported sendResetPassword / sendVerifyEmail helpers. Without it those endpoints reject the request rather than silently doing nothing.

Protect routes on a framework with no edge proxy

app/lib/require-auth.ts exports requireAuth(request), which validates via auth.api.getSession and throws a redirect Response that React Router short-circuits the loader on. Nuxt's server/middleware/auth.ts runs the same check per request for /dashboard and /settings.

Why Better Auth, specifically

note

Self-hosted: Better Auth owns the user/session/account/verification tables. This stack emits them (db/auth-schema.ts) and hands them to the Drizzle adapter, so app-type schemas can foreign-key `user` directly.

What Better Auth pulls in

bun add better-auth
BETTER_AUTH_SECRETgenerate with `openssl rand -base64 32`
BETTER_AUTH_URLyour app's base URL