# Nfs Add Auth

> Add Better Auth to an existing project scaffolded with nextjs-fullstack-starter that initially skipped auth. Use this when the user says 'add auth', 'add login', 'wire Better Auth', 'add user accounts', invokes /nfs-add-auth, or starts asking permission/session questions in a project without auth wired. Wires up the Better Auth Prisma adapter, session helper, requirePermission helper, /login page (credentials), the auth route handler at /api/auth/[...all], and the User / Session / Account / Verification Prisma models. Updates the (dashboard) layout to call requireSession() and routes Server Actions through requireSession() first.

- Skill: `juncoding/nfs-add-auth` (Agent Skill)
- Install (CLI): `npx skillmds@latest add juncoding/nfs-add-auth`
- Raw SKILL.md: https://api.skillmd.com/api/skills/juncoding/nfs-add-auth/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: juncoding (https://skillmd.com/u/juncoding)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/juncoding/nfs-add-auth

---


# 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:

1. `src/server/modules/` doesn't exist — the project wasn't scaffolded with this plugin.
2. `src/server/auth/index.ts` already exists — auth is already wired. Suggest `/nfs-review-project` instead.
3. The Prisma schema already has a `User` model 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:

1. Add Better Auth packages to `package.json`:
   - `better-auth`
   - `better-auth/adapters/prisma` (peer)
2. Add Prisma models for `User`, `Session`, `Account`, `Verification`.
3. Add RBAC models (`Role`, `Permission`, `UserRole`, `RolePermission`).
4. Write `src/server/auth/index.ts` (Better Auth config).
5. Write `src/server/auth/session.ts` (`requireSession`).
6. Write `src/server/auth/permissions.ts` (`requirePermission`).
7. Write `src/app/api/auth/[...all]/route.ts`.
8. Write `src/app/(auth)/login/page.tsx`.
9. Update `src/app/(dashboard)/layout.tsx` to call `requireSession()`.
10. Add `BETTER_AUTH_URL` and `BETTER_AUTH_SECRET` to `src/env.ts` and `.env.example`.
11. Seed default roles (`admin`, `viewer`) and one permission scope (`example:read`) so the example service has something to gate on.
12. Update `CLAUDE.md` to note that auth is now wired and document the `requireSession` / `requirePermission` pattern.
13. Walk the user through a `pnpm prisma migrate dev --name add_auth` and 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.ts`
- `auth-session.ts.template` → `src/server/auth/session.ts`
- `auth-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:

```ts
// 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}`);
```

```json
// 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":

```markdown
## 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:

```bash
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 take `userId`. 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.

