authjs-implementation

activetype/docdomain/auth

TL;DR

LumenDev Invoice file map for auth. Reusable pattern (templates, packages, env contract): [[Resources/Tech/Auth.js/Auth.js Next.js JWT Google MongoDB adapter pattern]]. This note lists this repo’s paths and APIs only. App operator detail: docs/utility/authentication.md, AGENTS.md.

Stack split (why two entry points)

| Layer | File | Role | | ----- | ---- | ---- | | Edge-safe config | src/auth.config.ts | No mongodb import. Exports NextAuthConfig: Google provider, pages.signIn, session.strategy: "jwt", trustHost, JWT/session callbacks that copy user.id into token and session.user.id. Consumed by middleware only. | | Full app auth | src/auth.ts | NextAuth({ ...authConfig, adapter: MongoDBAdapter(clientPromise) }) → exports handlers, auth, signIn, signOut. |

Middleware must not load the MongoDB driver (historically Edge / bundle issues). Route handlers and auth() run in Node and can use the adapter.

Session and identity

  • Strategy: JWT (session.strategy: "jwt" in auth.config.ts).
  • Lifetime: maxAge: 30 * 24 * 60 * 60 (30 days).
  • Session shape: src/types/next-auth.d.ts extends Session.user with required id: string; JWT module adds optional id on the token.
  • Callbacks: jwt sets token.id from user.id on first sign-in; session copies token.id to session.user.id.

HTTP routes and handlers

| Path | Implementation | | ---- | -------------- | | Auth catch-all | src/app/api/auth/[...nextauth]/route.ts re-exports GET / POST from handlers in src/auth.ts. | | Sign-in UI | src/app/sign-in/page.tsx — client page; signIn("google", { callbackUrl }) via next-auth/react; callbackUrl from query or default /. |

Middleware behavior

File: src/middleware.ts.

  • Builds auth via NextAuth(authConfig) only (no adapter).
  • Always allow: pathname under /api/auth, /_next, /sign-in, /favicon.ico.
  • Unauthenticated + /api/*: respond 401 JSON { error: "Unauthorized" }.
  • Unauthenticated + page: redirect to /sign-in with callbackUrl = path + search.
  • Matcher: broad match excluding static assets and common image extensions (see config.matcher in file).

API route guard (requireSession)

File: src/lib/requireSession.ts. Imports auth from @/auth (full config + adapter).

  • requireSession(){ session } or { error: NextResponse.json(..., 401) } if session.user.id missing.
  • requireSessionResponse()null or a 401 response (helper variant).

Used in (grep-verified): src/app/api/invoices/route.ts, invoices/[id]/route.ts, invoices/[id]/activity/route.ts, invoices/next-number/route.ts, invoices/migrate/route.ts, projects/route.ts, projects/[id]/route.ts, settings/company/route.ts.

Migrate: POST /api/invoices/migrate remains session-protected and should only run when NODE_ENV === "development" (app policy — see handler).

MongoDB adapter wiring

  • Client: src/lib/mongodb-auth.ts — native MongoClient, MONGODB_URI (fallback mongodb://localhost:27017/lumendev-invoice in code if env unset; startup assertEnv still requires MONGODB_URI in practice).
  • Dev: global _mongoAuthClientPromise reuse to avoid connection spam during HMR.
  • Prod: new client promise per cold start path in that file.
  • Collections: Auth.js / adapter default names (users, accounts, sessions, verification_tokens, etc.) live in the same database as Mongoose — not the same ODM; two persistence stacks on one URI.

Client React tree

  • src/components/AuthSessionProvider.tsx — wraps app with SessionProvider from next-auth/react.
  • src/app/layout.tsx wraps children with AuthSessionProvider so client hooks and signIn work under App Router.

Environment (summary)

Strict validation: src/env.ts assertEnv() — imported from next.config.ts so next dev / next build / next start exit if missing:

  • AUTH_SECRET or NEXTAUTH_SECRET
  • AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET
  • MONGODB_URI
  • AUTH_URL or NEXTAUTH_URL

AUTH_TRUST_HOST: not in assertEnv; trustHost in config is true when NODE_ENV === "development" or AUTH_TRUST_HOST===true (needed on Vercel previews / inferred host). Detail: app AGENTS.md and docs/utility/authentication.md.

Product / security notes (as-built)

  • No multi-tenant row-level logic: authenticated users share the same invoice dataset (hub Scope (out)).
  • Receipt routes require auth (no public share token in current codebase).
  • Operational triage: [[Projects/lumendev-invoice/runbooks/runbook-auth-failure]].

Related

  • Evergreen pattern: [[Resources/Tech/Auth.js/Auth.js Next.js JWT Google MongoDB adapter pattern]]
  • Vault: [[Projects/lumendev-invoice/changelog/2026-05-17-authjs-shipped]], [[Projects/lumendev-invoice/runbooks/runbook-auth-failure]], [[Projects/lumendev-invoice/context/index]]
  • App repo (relative to checkout): docs/utility/authentication.md, AGENTS.md, src/auth.ts, src/auth.config.ts, src/middleware.ts