Next.js example
End-to-end demo of @authcore/nextjs showing every flagship feature in one app: email/password sign-up, login, magic-link, Google OAuth (optional), TOTP 2FA, refresh tokens, CSRF, server-component user reads, and middleware route protection.
Source: examples/nextjs in the repo.
Run it
# from the repo root, so workspace packages resolve:
pnpm install
pnpm -w run build
# then:
cd examples/nextjs
cp .env.example .env.local
# edit .env.local and set AUTH_SECRET to a strong random string (≥ 32 chars)
pnpm db:init # creates auth.db (SQLite) with the AuthCore tables
pnpm dev # http://localhost:3000You can immediately:
- Sign up with email + password at
/signup - Sign in at
/login - Request a magic link at
/magic-link— the link prints to yourpnpm devterminal (no email provider needed for local testing) - Enable 2FA at
/settings, scan the QR with any authenticator app, then sign out and back in to see the challenge flow
Optional: Google OAuth
Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env.local. The "Sign in with Google" button appears on /login. Configure the Authorized redirect URI in Google Cloud Console as http://localhost:3000/api/auth/oauth/google/callback.
What's in each file
app/
├── api/auth/[...authcore]/route.ts ← single file = every auth endpoint
├── page.tsx ← landing
├── signup/page.tsx ← email + password registration
├── login/ ← password + OAuth + 2FA challenge UI
│ ├── page.tsx
│ └── LoginForm.tsx
├── magic-link/page.tsx ← passwordless email link request
├── dashboard/ ← protected; reads user via getCurrentUser
│ ├── page.tsx
│ └── LogoutButton.tsx
├── settings/ ← 2FA enrollment + disable
│ ├── page.tsx
│ └── TwoFactorSection.tsx
├── layout.tsx ← wraps app in <AuthProvider>
└── providers.tsx ← 'use client' boundary
lib/auth.ts ← AuthCore wiring (single source of truth)
db/ ← Drizzle SQLite schema + connection
middleware.ts ← edge-safe cookie presence checkArchitecture highlights
One catch-all route. app/api/auth/[...authcore]/route.ts re-exports GET and POST from lib/auth.ts. Every AuthCore endpoint — /register, /login, /refresh, /2fa/verify, /oauth/google, etc. — is served from this single file.
Server Components read the user with getCurrentUser. No client-side hydration round-trip: the dashboard page is rendered with the user already in hand:
const user = await getCurrentUser()
if (!user) redirect('/login?next=/dashboard')Middleware is edge-safe. It checks for cookie presence only — no JWT verification at the edge. Real verification happens in Server Components / Route Handlers (where the Node runtime can use jsonwebtoken).
Lazy DB initialization. db/index.ts returns a Proxy that resolves to the real Drizzle DB on first property access — avoids loading the SQLite native module during next build's page-data collection phase.
Cookie mode + CSRF. useCookies: true keeps the JWT in an HttpOnly cookie; csrf: true adds a synchronizer-token cookie that @authcore/core-web automatically echoes back on every state-changing request.
Swapping to Postgres
This example uses SQLite for zero-friction setup. To switch:
pnpm add pg @types/pg- In
db/index.ts, swap the driver:tsconst { drizzle } = require('drizzle-orm/node-postgres') const { Pool } = require('pg') _db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL })) - In
db/schema.ts, swap the schema import:tsexport { users, tokens, oauthAccounts, tokenTypeEnum } from '@authcore/drizzle-adapter/pg' - In
lib/auth.ts, swap@authcore/drizzle-adapter/sqlite→@authcore/drizzle-adapter/pg.
Same routes, same UI, different backing store.