Authentication
indiegoon uses Better Auth — a TypeScript-first auth library that handles everything from email/password to 2FA without external services.
What's Included
Out of the box, you get:
- •Email + Password — with email verification
- •OAuth — Google, GitHub, Twitter (add any provider)
- •Magic Links — passwordless sign-in via email
- •Two-Factor Auth — TOTP with 10 backup codes
- •Session Management — 30-day sessions, auto-refresh
- •Password Reset — secure token-based flow
How It Works
Auth is configured in two files:
- •
src/lib/auth/index.ts— Server config (providers, plugins, hooks) - •
src/lib/auth/client.ts— Client exports (signIn, signUp, useSession)
The catch-all API route at /api/auth/[...all] handles all auth endpoints automatically.
Configuration
Basic Setup
The CLI handles this for you (goon setup auth), but here's what's configured:
// src/lib/auth/index.ts
import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { db } from "@/lib/db"
export const auth = betterAuth({
database: drizzleAdapter(db),
session: {
expiresIn: 60 * 60 * 24 * 30, // 30 days
updateAge: 60 * 60 * 24, // refresh daily
},
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
minPasswordLength: 8,
},
})
Adding OAuth Providers
Set the env vars and they're automatically enabled:
# Google OAuth
GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="your-secret"
# GitHub OAuth
GITHUB_CLIENT_ID="your-client-id"
GITHUB_CLIENT_SECRET="your-secret"
# Twitter OAuth
TWITTER_CLIENT_ID="your-client-id"
TWITTER_CLIENT_SECRET="your-secret"
Each provider is conditionally registered — if the env vars exist, the provider is active. No code changes needed.
Enabling Magic Links
Magic links are enabled by default with a 15-minute expiry:
import { magicLink } from "better-auth/plugins"
plugins: [
magicLink({
expiresIn: 60 * 15, // 15 minutes
}),
]
Enabling Two-Factor Auth
TOTP-based 2FA with backup codes:
import { twoFactor } from "better-auth/plugins"
plugins: [
twoFactor({
issuer: "My SaaS",
totpBackupCodes: 10,
}),
]
Using Auth in Your Code
Server-Side (API Routes, Server Components)
import { auth } from "@/lib/auth"
// In an API route
export async function GET(request: Request) {
const session = await auth.api.getSession({
headers: request.headers,
})
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 })
}
return Response.json({ user: session.user })
}
Client-Side (React Components)
"use client"
import { useSession, signOut } from "@/lib/auth/client"
export function UserMenu() {
const { data: session, isPending } = useSession()
if (isPending) return <div>Loading...</div>
if (!session) return <a href="/sign-in">Sign In</a>
return (
<div>
<p>Hey, {session.user.name}</p>
<button onClick={() => signOut()}>Sign Out</button>
</div>
)
}
Sign In Programmatically
import { signIn } from "@/lib/auth/client"
// Email + Password
await signIn.email({ email, password, callbackURL: "/dashboard" })
// OAuth
await signIn.social({ provider: "google", callbackURL: "/dashboard" })
// Magic Link
await signIn.magicLink({ email, callbackURL: "/dashboard" })
Middleware (Route Protection)
The middleware at src/middleware.ts protects authenticated routes:
// Public paths (no auth required)
const publicPaths = [
"/", "/sign-in", "/sign-up",
"/forgot-password", "/pricing",
"/blog", "/terms", "/privacy",
"/api/auth", "/api/webhooks",
]
// Everything else requires a valid session
// Redirects to /sign-in with callbackUrl
The middleware also enforces rate limiting (20 requests per 10 seconds per IP) on all API routes except webhooks.
Auth Pages
Pre-built pages are included at:
- •
/sign-in— Email/password + magic link toggle + OAuth buttons - •
/sign-up— Registration with password strength meter - •
/forgot-password— Request password reset - •
/reset-password— Set new password from token - •
/verify-email— Email verification confirmation
All pages are styled and functional. Customize the copy, add fields, or restyle them to match your brand.
Email Templates
Auth emails (verification, reset, welcome, magic link) are defined in src/lib/email/templates/auth.ts. They use a responsive HTML base template with your branding.
Session Schema
Auth data is stored in four tables (auto-created by db:push):
| Table | Purpose |
|---|---|
users | User profiles (name, email, image) |
sessions | Active sessions (token, expiry, IP, user agent) |
accounts | OAuth accounts + credentials (multi-provider) |
verifications | Email verification + password reset tokens |