Two files. Ten security concerns you don't write yourself. One hour to ship.
app/routes/api/auth.$.tssequenceDiagram autonumber actor User participant Browser participant Route as TanStack Route participant BA as Better-Auth participant DB as Postgres participant Mail as Resend User->>Browser: Fill signup form Browser->>Route: POST /api/auth/sign-up Route->>BA: auth.handler(request) Note over BA: Hash password (Argon2id) BA->>DB: INSERT user BA->>DB: INSERT verification_token BA->>Mail: send verify email Mail-->>User: ✉ noreply@sitzio.de User->>Browser: Click verify link Browser->>Route: GET /api/auth/verify-email Route->>BA: auth.handler(request) Note over BA: Validate token, single-use BA->>DB: UPDATE user verified=true BA->>DB: DELETE verification_token BA->>DB: INSERT session BA-->>Browser: Set-Cookie session_token
(httpOnly, Secure, SameSite) Browser-->>User: ✓ Logged in
import { betterAuth } from 'better-auth' import { Pool } from 'pg' import { resend } from './resend' export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { enabled: true, requireEmailVerification: true, }, emailVerification: { sendVerificationEmail: async ({ user, url }) => { await resend.emails.send({ from: 'noreply@sitzio.de', to: user.email, subject: 'Verify your email', html: `<a href="${url}">Verify</a>`, }) }, }, })
// One file. All auth routes. import { createAPIFileRoute } from '@tanstack/start/api' import { auth } from '~/lib/auth' export const Route = createAPIFileRoute( '/api/auth/$' )({ GET: ({ request }) => auth.handler(request), POST: ({ request }) => auth.handler(request), }) // That's the entire wiring. // Better-Auth handles every // /api/auth/* endpoint internally.