codexmachina
Auth provider

Every verified stack secured with Auth.js (NextAuth).

Auth.js (NextAuth v5): self-hosted OAuth running inside your app against your database (Drizzle adapter).

80 verified stacks✓ 80 greenLast updated 2026-08-23How we verify →

80 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) · MySQL 8 · Resend
✓
Blog / CMSNext.js 16 (App Router) · MySQL 8
✓
Booking / schedulingNext.js 16 (App Router) · MySQL 8 · Resend
✓
CRMNext.js 16 (App Router) · MySQL 8 · Resend
✓
E-commerce storeNext.js 16 (App Router) · MySQL 8 · Resend
✓
Fintech ledgerNext.js 16 (App Router) · MySQL 8 · Resend
✓
Fitness trackerNext.js 16 (App Router) · MySQL 8 · Resend
✓
Forum / communityNext.js 16 (App Router) · MySQL 8
✓
Helpdesk / supportNext.js 16 (App Router) · MySQL 8
✓
IoT telemetryNext.js 16 (App Router) · MySQL 8
✓
Job boardNext.js 16 (App Router) · MySQL 8 · Resend
✓
LMS (learning platform)Next.js 16 (App Router) · MySQL 8 · Resend

What Auth.js (NextAuth) gives you

Auth.js v5 — still shipped as the next-auth package — runs inside your app and stores identity in your own database, but the shape it assumes is OAuth-first, and that decides most of what this cell looks like. No password is stored anywhere in the emitted schema. Sign-up and sign-in are the same act: the vendored screen is one "Continue with GitHub" button calling signIn("github", { redirectTo: "/dashboard" }), and the first successful callback is what creates the row. The identity tables are the canonical @auth/drizzle-adapter set, authored into db/auth-schema.ts as user, account, session and verificationToken. Their keys are worth reading before building on them.

account has no id of its own; its primary key is the composite (provider, providerAccountId), so one user row can hold several provider accounts, one per pair, each cascading from user.id. session is keyed by sessionToken and carries an expires timestamp — this is a database-session setup, not a JWT one, so signing out deletes a row and revocation is a DELETE you can run yourself. user.id is a text (varchar(255) on MySQL) primary key filled by $defaultFn(() => crypto.randomUUID()), and app-type schemas foreign-key it directly. src/lib/auth.ts hands all four tables to DrizzleAdapter explicitly through usersTable / accountsTable / sessionsTable / verificationTokensTable, so the adapter writes this schema rather than looking for its own default table names.

NextAuth's factory returns handlers, auth, signIn and signOut in a single destructure, and each of those lands somewhere specific in the emitted files. handlers is re-exported as GET and POST from src/app/api/auth/[...nextauth]/route.ts, which is where sign-in, callback, session and CSRF requests all terminate. auth is used twice at different strengths: wrapped as the proxy in src/proxy.ts, where a null req.auth redirects traffic off /dashboard and /settings, and called directly inside requireUser() in src/lib/session.ts for the real per-render check. <SessionProvider> in the root layout is what makes useSession() and signOut() work in the sidebar's nav-user. Two constraints follow. This adapter is pinned to Next through supportedFrameworks, because next-auth is Next-specific — React Router and Nuxt would need @auth/core instead.

And because GitHub already verified the address, there is no verify-email or reset-password flow here to wire: composing an email fragment gets you events.createUser calling sendWelcome once the adapter has persisted the user, and nothing more. Adding email or magic-link sign-in means adding a provider yourself, which changes the sign-in surface.

Built for

Sign in with a GitHub OAuth account

The (auth)/sign-in screen calls signIn("github", { redirectTo: "/dashboard" }) from next-auth/react; the callback terminates in src/app/api/auth/[...nextauth]/route.ts, which re-exports NextAuth's handlers as GET and POST. The sign-up screen is the same button with different copy.

Persist sessions in your own database

DrizzleAdapter(db, { usersTable: user, accountsTable: account, sessionsTable: session, verificationTokensTable: verificationToken }) in src/lib/auth.ts writes a session row keyed by sessionToken with an expires timestamp, over the same Drizzle client src/lib/db.ts exports.

Gate a page and the route it lives under

src/proxy.ts wraps the exported auth as middleware and redirects when req.auth is null for /dashboard and /settings; requireUser() in src/lib/session.ts awaits auth() again inside the page, so a stale cookie cannot render protected content.

Read the session in a client component

src/app/layout.tsx mounts <SessionProvider> inside <body>, which is what lets nav-user call useSession() for the avatar and email and signOut() for the menu action without threading the session down through props.

Add a second OAuth provider

providers: [GitHub] in src/lib/auth.ts is the whole provider list. account's composite (provider, providerAccountId) primary key and cascading user_id mean one user row can carry an account row per provider — the adapter stores the tokens, scope and expires_at per pair.

Why Auth.js (NextAuth), specifically

note

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

note

OAuth (GitHub) is wired by default — sign-in creates the account, so there is no separate email-signup step. Swap or add providers in src/lib/auth.ts.

What Auth.js (NextAuth) pulls in

bun add next-auth @auth/drizzle-adapter
AUTH_SECRETgenerate with `npx auth secret` (or `openssl rand -base64 32`)
AUTH_GITHUB_IDGitHub OAuth app client id
AUTH_GITHUB_SECRETGitHub OAuth app client secret