--- phase: "02-admin-area-interactive-features" plan: 01 subsystem: "auth" tags: [next-auth, credentials, jwt, admin, session, middleware] dependency_graph: requires: [] provides: [admin-session-auth, admin-login-page, proxy-admin-guard] affects: [src/proxy.ts, src/lib/auth.ts] tech_stack: added: [next-auth@4] patterns: [CredentialsProvider, JWT session strategy, edge proxy guard] key_files: created: - src/lib/auth.ts - src/app/api/auth/[...nextauth]/route.ts - src/app/admin/login/page.tsx modified: - src/proxy.ts - package.json - package-lock.json - .env.local decisions: - "Keep proxy export named 'proxy' (not 'middleware') — Next.js 16 renamed the middleware concept back to proxy, breaking the plan's rename instruction" - "Wrap useSearchParams() in Suspense boundary — required by Next.js App Router for static prerendering" - "Single admin user, env-var credentials only — no DB users table (stateless JWT)" metrics: duration: "~15 minutes" completed: "2026-05-15T08:42:35Z" tasks_completed: 2 files_changed: 7 --- # Phase 02 Plan 01: Auth.js Admin Session + Proxy Guard Summary Auth.js v4 CredentialsProvider with JWT sessions gates the entire /admin/* area using env-var credentials (no DB users table), with an edge proxy guard in src/proxy.ts that validates sessions via getToken() before any admin page code runs. ## What Was Built ### Task 1: next-auth@4 installation and auth config - Installed `next-auth@4` (stable; v5 still RC as of 2026-05-15) - Created `src/lib/auth.ts` — NextAuthOptions with CredentialsProvider reading `ADMIN_EMAIL` + `ADMIN_PASSWORD` from env vars; JWT session strategy (stateless, no DB adapter) - Created `src/app/api/auth/[...nextauth]/route.ts` — NextAuth catch-all handler (GET + POST) - Updated `.env.local` with `NEXTAUTH_URL`, `NEXTAUTH_SECRET` (32-byte base64), `ADMIN_EMAIL`, `ADMIN_PASSWORD` ### Task 2: Proxy guard and login page - Extended `src/proxy.ts` with `/admin/*` session guard using `getToken()` from `next-auth/jwt` - `/admin/login` and `/api/auth/*` exempted from the guard (pass-through) - Unauthenticated `/admin/*` requests redirect to `/admin/login?callbackUrl=` - `/c/:path*` client token validation logic preserved verbatim from Phase 1 - matcher updated: `["/admin/:path*", "/c/:path*"]` - Created `src/app/admin/login/page.tsx` — email+password Client Component with `signIn('credentials')`, inline error display ("Email o password non corretti."), redirect on success ## Commits | Task | Commit | Description | |------|--------|-------------| | 1 | 5d363a6 | feat(02-01): install next-auth@4, configure CredentialsProvider auth | | 2 | 69f8a7e | feat(02-01): extend proxy.ts with admin session guard, add login page | ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Next.js 16 proxy export name is 'proxy', not 'middleware'** - **Found during:** Task 2 first build attempt - **Issue:** The plan instructed renaming the export to `middleware` (Next.js 15 convention), but this project runs Next.js 16.2.6, which introduced the `proxy` concept and requires the function to be named `proxy`. The build failed with: "Proxy is missing expected function export name" - **Fix:** Kept the export name as `proxy` — consistent with the existing Phase 1 file and Next.js 16 API - **Files modified:** `src/proxy.ts` **2. [Rule 1 - Bug] useSearchParams() requires Suspense boundary in App Router** - **Found during:** Task 2 second build attempt - **Issue:** `useSearchParams()` in a Client Component causes a build failure during static page generation without a Suspense boundary. Error: "useSearchParams() should be wrapped in a suspense boundary at page /admin/login" - **Fix:** Extracted the form into `AdminLoginForm` component; wrapped it in `` inside the default export `AdminLoginPage` - **Files modified:** `src/app/admin/login/page.tsx` ## Known Stubs None — all implemented functionality is complete and functional. ## Threat Surface Scan No new security surface beyond what was planned in the threat model: - T-02-01: Mitigated — CredentialsProvider validates against env vars server-side - T-02-02: Mitigated — JWT signed with NEXTAUTH_SECRET, verified via getToken() on every /admin request - T-02-03: Mitigated — ADMIN_PASSWORD stored only in .env.local (gitignored) and Vercel secrets - T-02-04: Accepted — /api/auth/* exempt by design, NextAuth handles its own CSRF - T-02-05: Accepted — No rate limiting in v1 ## Self-Check: PASSED All created files exist on disk. Both task commits (5d363a6, 69f8a7e) verified in git log.