Add Better Auth to an existing scaffold
Use when the user has a project from nextjs-fullstack-starter that initially chose auth=skip and now wants to wire in real authentication.
Pre-flight checks
Refuse if any of these is true:
src/server/modules/doesn't exist — the project wasn't scaffolded with this plugin.src/server/auth/index.tsalready exists — auth is already wired. Suggest/nfs-review-projectinstead.- The Prisma schema already has a
Usermodel and the user hasn't asked to merge — clarify first whether to extend their model or replace it.
Plan, then confirm
Walk the user through what you're about to do and ask for confirmation before running anything:
- Add Better Auth packages to
package.json:better-authbetter-auth/adapters/prisma(peer)
- Add Prisma models for
User,Session,Account,Verification. - Add RBAC models (
Role,Permission,UserRole,RolePermission). - Write
src/server/auth/index.ts(Better Auth config). - Write
src/server/auth/session.ts(requireSession). - Write
src/server/auth/permissions.ts(requirePermission). - Write
src/app/api/auth/[...all]/route.ts. - Write
src/app/(auth)/login/page.tsx. - Update
src/app/(dashboard)/layout.tsxto callrequireSession(). - Add
BETTER_AUTH_URLandBETTER_AUTH_SECRETtosrc/env.tsand.env.example. - Seed default roles (
admin,viewer) and one permission scope (example:read) so the example service has something to gate on. - Update
CLAUDE.mdto note that auth is now wired and document therequireSession/requirePermissionpattern. - Walk the user through a
pnpm prisma migrate dev --name add_authand a quick smoke test (create a sysadmin via the seed, log in).
If any conflict with existing code is detected during steps 4-9, surface it before overwriting.
File templates
Use the same templates the scaffolder uses — they live in ../nfs-scaffold-app/assets/:
auth-index.ts.template→src/server/auth/index.tsauth-session.ts.template→src/server/auth/session.tsauth-permissions.ts.template→src/server/auth/permissions.ts
The Prisma model snippets are in nfs-scaffold-app/assets/prisma-schema.starter.prisma — copy the Auth + RBAC sections.
Seed sysadmin
Add a small CLI script for bootstrapping the first sysadmin:
// prisma/grant-sysadmin.ts
import { db } from "@/server/db/client";
const email = process.argv[2];
if (!email) {
console.error("Usage: pnpm run grant-sysadmin <email>");
process.exit(1);
}
await db.user.update({
where: { email },
data: { isSysadmin: true },
});
console.log(`Granted sysadmin to ${email}`);
// package.json scripts addition
"grant-sysadmin": "tsx prisma/grant-sysadmin.ts"
Document this in CLAUDE.md under "First admin setup".
Update CLAUDE.md
Add a block under "Stack":
## Auth
Better Auth with credentials. Sessions in Postgres via the `nextCookies` plugin.
- `requireSession()` — call at the top of every Server Component, Server Action, or route handler that needs an authenticated user.
- `requirePermission(userId, scope)` — call as the first line of every service method touching user-owned data.
- Sysadmin bootstrap: `pnpm run grant-sysadmin <email>`.
Verification
After the changes land:
pnpm install
pnpm prisma generate
pnpm prisma migrate dev --name add_auth
pnpm verify
If pnpm verify is green and the login page renders, you're done.
Anti-patterns to refuse
- Wiring NextAuth instead of Better Auth. The plugin's invariants assume Better Auth's session + permission shape. NextAuth can be made to work but doesn't have the MCP plugin story or the same RBAC ergonomics. If the user insists, explain the trade and ask them to override.
- Adding
requireSession()calls inside services. Services takeuserId. The session check is delivery-layer. - Storing the sysadmin as a checked-in user row. Bootstrap via the CLI; don't ship credentials in source.